XML-Sitemaps mit Claude Code erzeugen
Astro- und Node-Sitemaps mit hreflang, lastmod, robots.txt und Search-Console-Prüfung umsetzen.
Eine Sitemap ist ein öffentliches URL-Verzeichnis, keine Indexierungsgarantie
Wenn Claude Code viele Artikel, Dokumentationsseiten oder Produktseiten erzeugt, liegt das Risiko oft nicht in der Komponente selbst. Es liegt darin, ob Suchmaschinen die richtigen URL zuverlässig finden. Eine XML-Sitemap nennt die kanonischen URL, das Datum wichtiger Änderungen und die Beziehungen zwischen übersetzten Versionen.
Wichtig ist dabei Genauigkeit. Die aktuelle Google-Dokumentation sagt, dass Google priority und changefreq ignoriert. lastmod wird nur verwendet, wenn der Wert dauerhaft zur tatsächlichen Änderung passt. Der alte Ping-Endpunkt für Sitemaps ist ebenfalls abgeschaltet. Moderne Abläufe sollten daher robots.txt, Google Search Console und Prüfungen nach dem Deployment nutzen, nicht https://www.google.com/ping?sitemap=....
Dieser Leitfaden zeigt zwei Wege: die offizielle Astro-Integration und einen Node.js-Generator ohne externe Abhängigkeiten für mehrsprachige MDX-Sammlungen. Für den größeren SEO-Kontext passen dazu Claude Code SEO-Optimierung und Claude Code CI/CD-Einrichtung.
Offizielle Regeln für die Umsetzung
| Punkt | Praktische Entscheidung |
|---|---|
| URL | Absolute URL wie https://example.com/blog/post/ verwenden |
| Grenze | Nach 50.000 URL oder 50 MB unkomprimiert aufteilen |
| Kodierung | UTF-8 speichern und XML-Werte escapen |
lastmod | Nur echte wichtige Änderungen an Inhalt, strukturierten Daten oder Links abbilden |
priority / changefreq | Für Google weglassen |
| Mehrsprachigkeit | Jede URL listet sich selbst und alle Sprachalternativen |
| Einreichung | robots.txt und Search Console nutzen, keine Ping-Skripte |
Die wichtigsten Quellen sind der Google-Sitemap-Leitfaden, die Google-Meldung zum Ende des Pings, der Leitfaden für lokalisierte Versionen und das sitemaps.org-Protokoll.
Anwendungsfall 1: Astro-Seiten und Blog-Routen
Für eine statische Astro-Seite ist @astrojs/sitemap der einfachste Einstieg. Die Integration läuft während astro build und kann Sprachbeziehungen erzeugen, wenn die Routenstruktur eindeutig ist.
npx astro add sitemap
// astro.config.mjs
import { defineConfig } from 'astro/config';
import sitemap from '@astrojs/sitemap';
export default defineConfig({
site: 'https://claudecodelab.com',
integrations: [
sitemap({
filter: (page) => !page.includes('/draft/') && !page.includes('/preview/'),
i18n: {
defaultLocale: 'ja',
locales: {
ja: 'ja',
en: 'en',
zh: 'zh-CN',
ko: 'ko',
es: 'es',
fr: 'fr',
de: 'de',
pt: 'pt-BR',
hi: 'hi',
id: 'id',
},
},
}),
],
});
Der häufigste Fehler ist ein falscher site-Wert. localhost, Preview-Domains oder gemischte http- und https-URL gehören nicht in die veröffentlichte Sitemap. Google ruft die URL so ab, wie sie dort stehen. Sie müssen also zu den kanonischen URL der Seiten passen.
Anwendungsfall 2: Node.js-Generator für mehrsprachige MDX-Dateien
Ein eigener Generator ist sinnvoll, wenn Inhalte in blog, blog-en, blog-zh und ähnlichen Sammlungen liegen oder wenn updatedDate exakt als lastmod verwendet werden soll. Dieses Beispiel nutzt nur eingebaute Node.js-Module und schreibt public/sitemap.xml.
// scripts/generate-sitemap.mjs
import { mkdir, readdir, readFile, stat, writeFile } from 'node:fs/promises';
import path from 'node:path';
const SITE_URL = (process.env.SITE_URL ?? 'https://example.com').replace(/\/$/, '');
const OUT_DIR = 'public';
const OUT_FILE = path.join(OUT_DIR, 'sitemap.xml');
const collections = [
{ dir: 'site/src/content/blog', prefix: '/blog', hreflang: 'ja' },
{ dir: 'site/src/content/blog-en', prefix: '/en/blog', hreflang: 'en' },
{ dir: 'site/src/content/blog-zh', prefix: '/zh/blog', hreflang: 'zh-CN' },
{ dir: 'site/src/content/blog-ko', prefix: '/ko/blog', hreflang: 'ko' },
{ dir: 'site/src/content/blog-es', prefix: '/es/blog', hreflang: 'es' },
{ dir: 'site/src/content/blog-fr', prefix: '/fr/blog', hreflang: 'fr' },
{ dir: 'site/src/content/blog-de', prefix: '/de/blog', hreflang: 'de' },
{ dir: 'site/src/content/blog-pt', prefix: '/pt/blog', hreflang: 'pt-BR' },
{ dir: 'site/src/content/blog-hi', prefix: '/hi/blog', hreflang: 'hi' },
{ dir: 'site/src/content/blog-id', prefix: '/id/blog', hreflang: 'id' },
];
function escapeXml(value) {
return String(value).replace(/[<>&'"]/g, (char) => ({
'<': '<',
'>': '>',
'&': '&',
"'": ''',
'"': '"',
})[char]);
}
async function* walk(dir) {
let items;
try {
items = await readdir(dir, { withFileTypes: true });
} catch (error) {
if (error.code === 'ENOENT') return;
throw error;
}
for (const item of items) {
const fullPath = path.join(dir, item.name);
if (item.isDirectory()) {
yield* walk(fullPath);
} else if (/\.(md|mdx)$/.test(item.name)) {
yield fullPath;
}
}
}
function frontmatterOf(source) {
return source.match(/^---\n([\s\S]*?)\n---/)?.[1] ?? '';
}
function dateField(frontmatter, key) {
return frontmatter.match(new RegExp(`^${key}:\\s*["']?(\\d{4}-\\d{2}-\\d{2})`, 'm'))?.[1];
}
function routeSlug(collectionDir, filePath) {
return path
.relative(collectionDir, filePath)
.replace(/\\/g, '/')
.replace(/\.(md|mdx)$/, '')
.replace(/\/index$/, '');
}
function encodeRoute(slug) {
return slug.split('/').map(encodeURIComponent).join('/');
}
async function collectEntries() {
const bySlug = new Map();
for (const collection of collections) {
for await (const filePath of walk(collection.dir)) {
const source = await readFile(filePath, 'utf8');
const frontmatter = frontmatterOf(source);
if (/^draft:\s*true\s*$/m.test(frontmatter)) continue;
const info = await stat(filePath);
const slug = routeSlug(collection.dir, filePath);
const lastmod =
dateField(frontmatter, 'updatedDate') ??
dateField(frontmatter, 'pubDate') ??
info.mtime.toISOString().slice(0, 10);
const route = `${collection.prefix}/${encodeRoute(slug)}/`;
const variant = {
loc: `${SITE_URL}${route}`,
hreflang: collection.hreflang,
lastmod,
};
const variants = bySlug.get(slug) ?? [];
variants.push(variant);
bySlug.set(slug, variants);
}
}
return [...bySlug.values()].flatMap((variants) =>
variants.map((variant) => ({
...variant,
alternates: variants.map(({ hreflang, loc }) => ({ hreflang, loc })),
})),
);
}
function buildSitemap(entries) {
const urls = entries.map((entry) => ` <url>
<loc>${escapeXml(entry.loc)}</loc>
<lastmod>${entry.lastmod}</lastmod>
${entry.alternates.map((alt) => ` <xhtml:link rel="alternate" hreflang="${escapeXml(alt.hreflang)}" href="${escapeXml(alt.loc)}" />`).join('\n')}
</url>`).join('\n');
return `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
xmlns:xhtml="http://www.w3.org/1999/xhtml">
${urls}
</urlset>
`;
}
const entries = await collectEntries();
if (entries.length === 0) {
throw new Error('Keine öffentlichen URL für die Sitemap gefunden.');
}
await mkdir(OUT_DIR, { recursive: true });
await writeFile(OUT_FILE, buildSitemap(entries), 'utf8');
console.log(`${entries.length} URL in ${OUT_FILE} geschrieben.`);
Ausführen:
SITE_URL=https://claudecodelab.com node scripts/generate-sitemap.mjs
Anwendungsfall 3: Artikel, Produkte und Dokumentation trennen
Ein kleiner Blog kommt mit einer sitemap.xml aus. Bei größeren Websites ist eine Trennung nach Inhaltstyp besser. So bleiben die Dateien unter den offiziellen Grenzen und Probleme lassen sich in Search Console leichter eingrenzen.
<?xml version="1.0" encoding="UTF-8"?>
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<sitemap>
<loc>https://example.com/sitemap-pages.xml</loc>
<lastmod>2026-06-03</lastmod>
</sitemap>
<sitemap>
<loc>https://example.com/sitemap-blog.xml</loc>
<lastmod>2026-06-03</lastmod>
</sitemap>
<sitemap>
<loc>https://example.com/sitemap-products.xml</loc>
<lastmod>2026-06-03</lastmod>
</sitemap>
</sitemapindex>
Claude Code sollte pro Datei die URL-Anzahl protokollieren, vor 50.000 URL aufteilen und im Index nur Sitemaps derselben Website referenzieren.
robots.txt, Search Console und Prüfung
Tragen Sie die Sitemap in robots.txt ein:
User-agent: *
Allow: /
Sitemap: https://claudecodelab.com/sitemap.xml
Danach wird die URL einmal in Google Search Console eingereicht. Im Deployment sollte zusätzlich geprüft werden, dass die öffentliche URL HTTP 200 liefert und eine gültige Sitemap-Wurzel enthält.
// scripts/verify-sitemap.mjs
const sitemapUrl = process.env.SITEMAP_URL ?? 'https://example.com/sitemap.xml';
const response = await fetch(sitemapUrl);
if (!response.ok) {
throw new Error(`Sitemap-Anfrage fehlgeschlagen: HTTP ${response.status}`);
}
const xml = await response.text();
if (!xml.includes('<urlset') && !xml.includes('<sitemapindex')) {
throw new Error('Die Antwort sieht nicht wie eine XML-Sitemap aus.');
}
console.log(`${sitemapUrl} geprüft. Größe: ${xml.length} bytes`);
Typische Fehler vor der Veröffentlichung
Der größte Fehler ist, alle lastmod-Werte auf das Build-Datum zu setzen. Wenn der Inhalt nicht geändert wurde, darf sich auch das Datum nicht ändern.
Der zweite Fehler ist das Einfügen von Entwürfen, noindex-Seiten, Weiterleitungsquellen oder Duplikaten. Eine Sitemap enthält nur die kanonischen URL, die in der Suche erscheinen sollen.
Der dritte Fehler betrifft hreflang. Jede Sprachversion muss auf sich selbst und auf alle anderen Sprachversionen zeigen. Einseitige Beziehungen sind unzuverlässig.
Der vierte Fehler ist fehlendes XML-Escaping. Ein & in einer Abfragezeichenfolge muss in XML als & geschrieben werden.
Monetarisierung und Ergebnis aus der Praxis
Eine Sitemap bringt nicht direkt Umsatz. Sie sichert aber die Auffindbarkeit von Seiten, die Umsatz vorbereiten: Tutorials, Vergleiche, kostenlose Ressourcen und Beratungsseiten. Nach der Korrektur sollten interne Links und CTA geprüft werden, damit Leser natürlich zur Claude Code Schulung oder zu passenden Ressourcen weitergehen.
In Masas ClaudeCodeLab-Ablauf waren drei Änderungen am wertvollsten: alter Ping-Code wurde entfernt, lastmod folgt updatedDate, und zehn Sprachversionen sind über gegenseitiges hreflang verbunden. Die redaktionelle Prüfung wurde klarer, weil Sitemap, Slug und MDX-frontmatter dieselben Daten widerspiegeln.
Kostenloses PDF: Claude-Code-Cheatsheet
E-Mail eintragen und eine Seite mit Befehlen, Review-Gewohnheiten und sicheren Workflows herunterladen.
Wir schützen Ihre Daten und senden keinen Spam.
Über den Autor
Masa
Engineer für praktische Claude-Code-Workflows und Team-Einführung.
Ähnliche Artikel
Claude Code Workflow von Obsidian zu CLAUDE.md
Obsidian-Arbeitsnotizen in CLAUDE.md-Betriebsnotizen verwandeln und Kontext nicht ständig neu erklären.
Claude Code Revenue CTA Routing: Artikel zu PDF, Gumroad und Beratung führen
Ein Claude-Code-Ablauf, der Leser nach Absicht zu Gratis-PDF, Gumroad oder Beratung führt.
Claude-Code-Team-Handoff-Regeln: Belege, Berechtigungen, Rollback und Umsatzpfade
Ein praktisches Claude-Code-Handoff für Review-Belege, Berechtigungen, Rollback, Gratis-PDF, Gumroad und Beratung.