Use Cases (Atualizado: 03/06/2026)

Gerar sitemaps XML com Claude Code

Crie sitemaps em Astro e Node com hreflang, lastmod, robots.txt e validação no Search Console.

Gerar sitemaps XML com Claude Code

Um sitemap é um inventário de URLs públicas, não uma garantia de indexação

Quando Claude Code ajuda a publicar muitos artigos, páginas de produto ou documentação, o ponto fraco nem sempre é o layout. Muitas vezes é a descoberta das URLs certas pelos mecanismos de busca. Um sitemap XML informa quais URLs canônicas devem ser rastreadas, quando elas tiveram uma atualização relevante e como as versões traduzidas se relacionam.

O ponto central é manter a informação verdadeira. A documentação atual do Google diz que priority e changefreq são ignorados, e que lastmod só é útil quando reflete mudanças reais de forma consistente. O antigo endpoint de ping para sitemaps também foi descontinuado. Portanto, o fluxo moderno deve usar robots.txt, Google Search Console e verificações de deploy, não chamadas para https://www.google.com/ping?sitemap=....

Este guia mostra dois caminhos práticos: a integração oficial do Astro e um gerador em Node.js sem dependências para coleções MDX multilíngues. Para encaixar isso na estratégia geral, veja também otimização de SEO com Claude Code e configuração de CI/CD com Claude Code.

Regras oficiais para seguir

ItemDecisão prática
URLUse URLs absolutas, como https://example.com/blog/post/
LimiteDivida ao chegar perto de 50.000 URLs ou 50 MB sem compactação
CodificaçãoSalve em UTF-8 e escape valores XML
lastmodUse a data real de uma mudança importante no conteúdo, dados estruturados ou links
priority / changefreqPodem ser omitidos para Google
Páginas multilínguesCada URL lista a si mesma e todas as versões alternativas
EnvioUse robots.txt e Search Console; remova scripts de ping

As fontes principais são o guia de sitemaps do Google, o aviso sobre o fim do ping, o guia de versões localizadas e o protocolo do sitemaps.org.

Caso 1: páginas Astro e rotas de blog

Em um site Astro estático, comece com @astrojs/sitemap. A integração roda durante astro build e pode gerar relações de idioma quando a estrutura de rotas é previsível.

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

O erro mais comum é deixar o valor de site errado. localhost, domínio de prévia ou mistura de http com https não devem aparecer no sitemap público. O Google tenta rastrear exatamente as URLs listadas, então elas precisam bater com as URLs canônicas.

Caso 2: gerador Node.js para MDX multilíngue

Um gerador próprio é útil quando o conteúdo fica em coleções como blog, blog-en e blog-zh, ou quando updatedDate precisa virar lastmod. O exemplo abaixo usa apenas módulos nativos do Node.js e grava 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('Nenhuma URL pública foi encontrada para o sitemap.');
}

await mkdir(OUT_DIR, { recursive: true });
await writeFile(OUT_FILE, buildSitemap(entries), 'utf8');
console.log(`${entries.length} URLs gravadas em ${OUT_FILE}.`);

Execute assim:

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

Caso 3: separar artigos, produtos e documentação

Um blog pequeno pode usar apenas sitemap.xml. Em sites maiores, separar por tipo de conteúdo facilita a manutenção, evita os limites oficiais e ajuda a depurar no 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>

Peça ao Claude Code para registrar a quantidade de URLs por arquivo, dividir antes de 50.000 URLs e garantir que o índice aponte apenas para sitemaps do mesmo site.

robots.txt, Search Console e validação

Adicione o sitemap ao robots.txt:

User-agent: *
Allow: /

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

Depois, envie a URL uma vez no Google Search Console. No deploy, confirme que a URL pública retorna HTTP 200 e parece um arquivo de 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(`Falha ao buscar o sitemap: HTTP ${response.status}`);
}

const xml = await response.text();
if (!xml.includes('<urlset') && !xml.includes('<sitemapindex')) {
  throw new Error('A resposta não parece um sitemap XML.');
}

console.log(`${sitemapUrl} validado. Tamanho: ${xml.length} bytes`);

Erros comuns antes de publicar

O primeiro erro é definir todos os lastmod como a data do build. Se o conteúdo não mudou, a data também não deve mudar.

O segundo é incluir rascunhos, páginas noindex, URLs de redirecionamento ou duplicadas. O sitemap deve listar apenas URLs canônicas que você quer ver nos resultados de busca.

O terceiro é criar hreflang em uma direção só. Cada versão de idioma precisa apontar para si mesma e para todas as outras.

O quarto é esquecer o escape XML. Um & em uma query string deve virar &amp;.

Monetização e resultado prático

O sitemap não monetiza sozinho, mas protege a descoberta das páginas que geram valor: tutoriais, comparativos, materiais gratuitos e páginas de consultoria. Depois de corrigir o sitemap, revise links internos e CTA para levar o leitor naturalmente à formação em Claude Code ou a recursos relacionados.

No fluxo de Masa para o ClaudeCodeLab, as mudanças mais úteis foram remover o ping antigo, alinhar lastmod com updatedDate e conectar as dez versões de idioma com hreflang recíproco. A revisão editorial ficou mais simples porque o sitemap passou a refletir as mesmas datas e slugs verificados no frontmatter MDX.

#Claude Code #sitemap #SEO #XML #automação
Grátis

PDF grátis: cheatsheet do Claude Code

Informe seu e-mail e baixe uma página com comandos, hábitos de revisão e workflows seguros.

Cuidamos dos seus dados e não enviamos spam.

Masa

Sobre o autor

Masa

Engenheiro focado em workflows práticos com Claude Code.