Use Cases (Aktualisiert: 3.6.2026)

XML-Sitemaps mit Claude Code erzeugen

Astro- und Node-Sitemaps mit hreflang, lastmod, robots.txt und Search-Console-Prüfung umsetzen.

XML-Sitemaps mit Claude Code erzeugen

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

PunktPraktische Entscheidung
URLAbsolute URL wie https://example.com/blog/post/ verwenden
GrenzeNach 50.000 URL oder 50 MB unkomprimiert aufteilen
KodierungUTF-8 speichern und XML-Werte escapen
lastmodNur echte wichtige Änderungen an Inhalt, strukturierten Daten oder Links abbilden
priority / changefreqFür Google weglassen
MehrsprachigkeitJede URL listet sich selbst und alle Sprachalternativen
Einreichungrobots.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) => ({
    '<': '&lt;',
    '>': '&gt;',
    '&': '&amp;',
    "'": '&apos;',
    '"': '&quot;',
  })[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 &amp; 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.

#Claude Code #Sitemap #SEO #XML #Automatisierung
Kostenlos

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.

Masa

Über den Autor

Masa

Engineer für praktische Claude-Code-Workflows und Team-Einführung.