Use Cases (Mis à jour: 03/06/2026)

Générer des sitemaps XML avec Claude Code

Créez des sitemaps Astro et Node avec hreflang, lastmod, robots.txt et contrôles Search Console.

Générer des sitemaps XML avec Claude Code

Un sitemap est un inventaire d’URL publiques, pas une promesse d’indexation

Quand Claude Code sert à publier de nombreux articles, pages produit ou documents, le point fragile n’est pas toujours le modèle de page. C’est souvent la découverte des bonnes URL par les moteurs de recherche. Un sitemap XML indique les URL canoniques à explorer, la date d’une modification importante et les relations entre versions traduites.

Il faut toutefois rester strict. La documentation actuelle de Google précise que priority et changefreq sont ignorés, et que lastmod n’est utile que s’il reste fidèle aux changements réels. L’ancien endpoint de ping des sitemaps est aussi abandonné. Un flux moderne doit donc passer par robots.txt, Google Search Console et des vérifications après déploiement, pas par https://www.google.com/ping?sitemap=....

Ce guide montre deux méthodes concrètes : l’intégration officielle d’Astro et un générateur Node.js sans dépendances pour des collections MDX multilingues. Pour replacer ce travail dans une stratégie plus large, consultez aussi l’optimisation SEO avec Claude Code et la configuration CI/CD avec Claude Code.

Les règles officielles à fixer avant de coder

PointDécision pratique
URLUtiliser des URL absolues comme https://example.com/blog/post/
LimiteDiviser au-delà de 50 000 URL ou 50 Mo non compressés
EncodageEnregistrer en UTF-8 et échapper les valeurs XML
lastmodIndiquer la date réelle d’un changement significatif
priority / changefreqLes omettre pour Google est acceptable
Pages multilinguesChaque URL liste elle-même et toutes ses variantes
SoumissionUtiliser robots.txt et Search Console, pas un script de ping

Les références utiles sont le guide des sitemaps de Google, l’annonce sur la fin du ping, le guide des versions localisées et le protocole sitemaps.org.

Cas 1 : pages Astro et routes de blog

Pour un site Astro statique, commencez par @astrojs/sitemap. L’intégration s’exécute pendant astro build et peut ajouter les relations de langue si la structure des routes est stable.

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',
        },
      },
    }),
  ],
});

L’erreur courante consiste à garder une mauvaise valeur dans site : localhost, un domaine de prévisualisation, ou un mélange de http et https. Google explore les URL telles qu’elles sont écrites. Elles doivent donc correspondre aux URL canoniques de vos pages.

Cas 2 : générateur Node.js pour MDX multilingue

Un script personnalisé devient utile quand le contenu est réparti dans blog, blog-en, blog-zh, ou quand updatedDate doit être utilisé comme lastmod. L’exemple suivant n’utilise que les modules natifs de Node.js et écrit 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('Aucune URL publique trouvée pour le sitemap.');
}

await mkdir(OUT_DIR, { recursive: true });
await writeFile(OUT_FILE, buildSitemap(entries), 'utf8');
console.log(`${entries.length} URL écrites dans ${OUT_FILE}.`);

Exécution :

SITE_URL=https://claudecodelab.com node scripts/generate-sitemap.mjs

Cas 3 : séparer articles, produits et documentation

Un petit blog peut garder un seul sitemap.xml. Dès que les pages augmentent, divisez par type de contenu. Cela évite les limites officielles et facilite le diagnostic dans Search Console.

<?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>

Demandez à Claude Code de journaliser le nombre d’URL par fichier, de découper avant 50 000 URL et de ne référencer dans l’index que des sitemaps du même site.

robots.txt, Search Console et vérification

Ajoutez le sitemap à robots.txt :

User-agent: *
Allow: /

Sitemap: https://claudecodelab.com/sitemap.xml

Soumettez ensuite cette URL dans Google Search Console. Dans le déploiement, vérifiez que l’URL publique répond en HTTP 200 et ressemble bien à un fichier sitemap.

// 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(`Échec de la requête sitemap : HTTP ${response.status}`);
}

const xml = await response.text();
if (!xml.includes('<urlset') && !xml.includes('<sitemapindex')) {
  throw new Error('La réponse ne ressemble pas à un sitemap XML.');
}

console.log(`${sitemapUrl} vérifié. Taille : ${xml.length} bytes`);

Échecs fréquents à corriger avant publication

Le premier piège consiste à mettre la date du build dans tous les lastmod. Si le contenu n’a pas changé, la date ne doit pas changer.

Le deuxième consiste à inclure des brouillons, des pages noindex, des sources de redirection ou des doublons. Le sitemap doit lister les URL canoniques que vous souhaitez voir dans les résultats.

Le troisième concerne hreflang : chaque version linguistique doit pointer vers elle-même et vers toutes les autres versions. Une relation à sens unique est fragile.

Le quatrième est l’oubli de l’échappement XML. Dans une URL, & doit devenir &amp;.

Monétisation et résultat observé

Un sitemap ne génère pas de revenus tout seul, mais il protège la découverte des pages qui comptent : tutoriels, comparatifs, ressources gratuites et pages de conseil. Après la correction, vérifiez les liens internes et les CTA pour guider naturellement les lecteurs vers la formation Claude Code ou les ressources associées.

Dans le flux ClaudeCodeLab de Masa, les changements les plus utiles ont été la suppression du vieux ping, l’alignement de lastmod sur updatedDate, et les liens hreflang réciproques entre les dix langues. La revue éditoriale est devenue plus simple, car le sitemap reflétait les mêmes dates et slugs que le frontmatter MDX.

#Claude Code #plan de site #SEO #XML #automatisation
Gratuit

PDF gratuit: cheatsheet Claude Code

Saisissez votre email et téléchargez une page avec commandes, habitudes de review et workflow sûr.

Nous protégeons vos données et n'envoyons pas de spam.

Masa

À propos de l'auteur

Masa

Ingénieur spécialisé dans les workflows pratiques avec Claude Code.