Sitemap que ajuda a busca: só URLs que respondem 200 e que você quer no índice
Gere o sitemap a partir do que está publicado, tire redirects, rascunhos e páginas noindex, use lastmod de verdade e confira cada URL com um script antes de enviar.
Um blog com 300 posts publicados tinha 1.140 URLs no sitemap. Estavam lá o slug antigo de cada post que mudou de nome, os rascunhos, as páginas de tag marcadas com noindex e as mesmas categorias com ?ordem=recente. A resposta curta: o sitemap deve listar só as URLs canônicas que respondem 200 e que você quer ver na busca. Ele é a lista de recomendações, não o inventário de tudo que já existiu.
Um sitemap sujo não derruba o site, mas manda a busca gastar visitas em endereço que você mesmo não recomenda. E esconde o sinal que importa: quando você publica um post novo, ele se perde no meio de mil URLs ruins.
O que o sitemap faz e o que ele não faz
A documentação do Google descreve o sitemap como um arquivo que informa quais páginas e arquivos você considera importantes, e quando mudaram. Ele ajuda a descobrir URLs, principalmente em sites grandes, novos ou com poucos links internos. Mas ele não garante indexação: é uma sugestão, e a busca decide.
Algumas regras que o Google documenta e que mudam como você monta o arquivo:
- Limite por arquivo: 50.000 URLs ou 50 MB sem compressão. Acima disso, divida em vários sitemaps e crie um arquivo índice que aponta para eles.
- URLs absolutas e canônicas: com protocolo e host exatamente como na canonical da página. Se o site responde em
https://www., o sitemap não listahttp://nem o domínio sem www. lastmodconta se for confiável. O Google usa a data quando ela é consistente com a realidade. Data que muda em todo deploy, sem o conteúdo mudar, perde valor.priorityechangefreqsão ignorados pelo Google. Não gaste tempo calibrando esses campos.
O que entra e o que fica de fora
Use esta pergunta para cada URL: "Eu quero que esta página apareça na busca, neste endereço exato?" Se a resposta for não, ela sai.
- Entra: posts publicados, categorias que você quer ranquear, páginas institucionais úteis, sempre no endereço canônico.
- Fica de fora: URLs que redirecionam, rascunhos, páginas com
noindex, versões com parâmetros de ordenação ou rastreamento, páginas de busca interna, páginas que exigem login.
A combinação mais confusa é a URL no sitemap com canonical apontando para outra. Você diz "indexe esta" no sitemap e "a certa é a outra" na página. O guia sobre canonical que aponta para a URL certa explica como alinhar as duas.
Como gerar o sitemap a partir do banco
O sitemap confiável é gerado da mesma fonte que decide o que é publicado. Nada de arquivo editado à mão. Exemplo em Node com mysql2 e Express:
import express from 'express';
import mysql from 'mysql2/promise';
const BASE = 'https://www.exemplo.com.br';
const pool = mysql.createPool({
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
});
function escaparXml(texto) {
return String(texto)
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
async function gerarSitemap() {
const [posts] = await pool.query(
"SELECT slug, atualizado_em FROM posts WHERE status = 'publicado' AND noindex = 0 ORDER BY atualizado_em DESC"
);
const itens = posts.map((p) =>
' <url><loc>' + escaparXml(BASE + '/blog/' + p.slug) + '</loc>' +
'<lastmod>' + new Date(p.atualizado_em).toISOString().slice(0, 10) + '</lastmod></url>'
);
return [
'<?xml version="1.0" encoding="UTF-8"?>',
'<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">',
...itens,
'</urlset>',
].join('\n');
}
const app = express();
app.get('/sitemap.xml', async (req, res, next) => {
try {
const xml = await gerarSitemap();
res.type('application/xml').send(xml);
} catch (err) {
next(err);
}
});
O ponto central está no WHERE: o mesmo critério que decide se o post aparece no site decide se ele entra no sitemap. Se a página de um post responde 404 para rascunho, o rascunho nunca chega ao arquivo.
O campo atualizado_em deve mudar quando o conteúdo muda de verdade (texto, título, código), não quando alguém conta uma visualização. Se o seu sistema atualiza essa coluna por qualquer motivo, crie uma coluna separada para "conteúdo alterado em".
Para site com muitas visitas de robô, guarde o XML gerado em cache por alguns minutos ou regenere ao publicar. A consulta acima é leve, mas não precisa rodar a cada pedido.
Avise onde está o sitemap
Coloque a linha no robots.txt, com URL absoluta:
User-agent: *
Allow: /
Sitemap: https://www.exemplo.com.br/sitemap.xml
E envie o endereço no Search Console, na seção de sitemaps. Lá você vê quantas URLs foram lidas e se houve erro de leitura.
Como conferir cada URL antes de enviar
Este script lê o sitemap e pede cada URL sem seguir redirecionamento. Tudo que não for 200, ou que tiver noindex, aparece na saída:
// conferir-sitemap.mjs (Node 18 ou mais novo)
const SITEMAP = 'https://www.exemplo.com.br/sitemap.xml';
const NOINDEX = new RegExp('<meta[^>]+name=["\']robots["\'][^>]+noindex', 'i');
const xml = await (await fetch(SITEMAP)).text();
const urls = [...xml.matchAll(new RegExp('<loc>([^<]+)</loc>', 'g'))].map((m) => m[1].trim());
let problemas = 0;
for (const url of urls) {
try {
const res = await fetch(url, { redirect: 'manual' });
const html = res.status === 200 ? await res.text() : '';
const robotsHeader = res.headers.get('x-robots-tag') || '';
if (res.status !== 200) {
console.log(res.status + ' ' + url + ' -> ' + (res.headers.get('location') || ''));
problemas++;
} else if (NOINDEX.test(html) || robotsHeader.includes('noindex')) {
console.log('noindex ' + url);
problemas++;
}
} catch (err) {
console.log('falhou ' + url + ' ' + err.message);
problemas++;
}
}
console.log(urls.length + ' URLs, ' + problemas + ' com problema');
process.exitCode = problemas > 0 ? 1 : 0;
Como o script termina com código 1 quando acha problema, ele pode rodar no CI depois de cada deploy. No blog do exemplo, a primeira execução listou 612 redirects, 140 páginas noindex e 88 rascunhos. Depois da correção do WHERE, o arquivo ficou com as 300 URLs que importavam.
Se a saída mostrar redirects encadeados, vale ler sobre cadeias de redirect que gastam a paciência e a busca.
Erros comuns
- Sitemap gerado uma vez e esquecido. Posts novos não entram e os removidos ficam lá, apontando para 404.
- URLs que redirecionam. Cada uma obriga a busca a seguir um salto para chegar ao destino que você deveria ter listado direto.
- Host misturado. Metade das URLs com www, metade sem. A busca recebe sinais contraditórios sobre qual é a canônica.
lastmodsempre igual à data de hoje. A data perde credibilidade e deixa de ajudar a busca a priorizar o que mudou.- Rascunho e noindex no arquivo. Você pede indexação e proíbe ao mesmo tempo. O relatório do Search Console se enche de avisos que escondem os reais.
- Caracteres sem escape. Um
&cru numa URL com parâmetro torna o XML inválido, e o arquivo inteiro pode ser rejeitado.
Checklist
- O sitemap é gerado pela mesma consulta que decide o que está publicado.
- O script de conferência termina com zero problemas.
- Todas as URLs usam o mesmo protocolo e host da canonical.
lastmodmuda só quando o conteúdo muda.- O
robots.txtaponta para o sitemap com URL absoluta. - O sitemap foi enviado no Search Console e não mostra erro de leitura.
- Arquivos acima de 50.000 URLs foram divididos com um índice.
Para fechar: conte as URLs do seu sitemap e compare com o número de páginas que você publicou de verdade. Os dois números batem?
Referência: https://developers.google.com/search/docs/crawling-indexing/sitemaps/overview