IndexNow: avise o Bing no segundo em que sua página muda (implementação real em Next.js 16)
Quem já publicou um site novo conhece a espera: a página está no ar, o sitemap foi enviado, e o buscador aparece quando quer. Dias para indexar um artigo; semanas para perceber que uma página saiu do ar. O modelo clássico é pull: o robô decide quando voltar, com base no que acha que o seu site merece. Para um domínio recém-criado, isso é pouco.
O IndexNow inverte a direção. Em vez de esperar o rastreador, o seu servidor avisa o buscador no instante em que uma URL foi criada, alterada ou removida. É um POST com uma lista de URLs. Só isso. Neste texto explico o protocolo, por que ele vale a pena mesmo o Google não participando, e mostro como implementei num site real em Next.js 16, o seigokanbrasil.com.br, onde todo conteúdo vem de um CMS e nada passa por rebuild.
O que é o IndexNow
É um protocolo aberto, lançado em outubro de 2021 pela Microsoft (Bing) e pela Yandex. Hoje participam Bing, Yandex, Seznam, Naver e Yep. A ideia cabe em três frases:
- Você gera uma chave (8 a 128 caracteres: letras, números e hífen) e a publica num arquivo de texto na raiz do domínio:
https://seudominio.com/<chave>.txt, contendo só a chave. Isso prova que quem envia é dono do host. - Quando uma URL muda, você faz um
GETou umPOSTpara o endpoint do protocolo com a URL (ou uma lista de até 10.000) e a chave. - Enviar para um motor é enviar para todos. O
api.indexnow.orgrepassa para os demais participantes. Um POST, vários índices.
O POST tem esta forma:
POST https://api.indexnow.org/indexnow
Content-Type: application/json; charset=utf-8
{
"host": "seigokanbrasil.com.br",
"key": "a1b2c3...",
"keyLocation": "https://seigokanbrasil.com.br/a1b2c3....txt",
"urlList": [
"https://seigokanbrasil.com.br/eventos/seminario-nacional-2026",
"https://seigokanbrasil.com.br/en/events/seminario-nacional-2026"
]
}
As respostas são poucas e dizem tudo:
| Código | Significado |
|---|---|
200 | URLs recebidas |
202 | Recebidas; a chave ainda vai ser validada |
400 | Formato inválido |
403 | Chave não encontrada ou não bate com o arquivo |
422 | URLs não pertencem ao host (ou chave fora do padrão) |
429 | Você está enviando demais |
O que o protocolo não promete: ranking. IndexNow é sobre descoberta e atualização, não sobre posição. Ele reduz o tempo entre "publiquei" e "o buscador sabe", e reduz rastreamento inútil (o robô para de visitar páginas que não mudaram para descobrir que não mudaram).
"Mas o Google não usa"
Verdade. O Google anunciou em 2021 que testaria o protocolo e, até onde se sabe, não adotou. Se o seu único objetivo é o Google, o caminho continua sendo sitemap com lastmod honesto, Search Console e links internos.
Ainda assim, há três motivos para implementar:
1. O índice do Bing está em mais lugares do que parece. DuckDuckGo, Ecosia e Yahoo usam resultados do Bing. O Copilot usa o Bing. E parte relevante dos assistentes de IA que fazem busca na web consulta índices que recebem IndexNow; o ChatGPT, por exemplo, declarou usar o índice do Bing entre as suas fontes. Entrar no índice do Bing em minutos, em vez de semanas, significa aparecer nas respostas que essas ferramentas citam.
2. O custo é um POST. Se o seu site já sabe quando uma página mudou (e qualquer site com CMS sabe), o IndexNow são vinte linhas.
3. Remoções. Quando uma página sai do ar, você pode enviar a URL também. O motor rastreia, recebe 404 ou 410, e remove do índice. Sem IndexNow, uma página apagada pode continuar aparecendo nos resultados por meses.
O caso: um site que nunca faz rebuild
O seigokanbrasil.com.br é o site da Seigokan Brasil, organização de Karate-Do Goju-ryu. É um Next.js 16 (App Router, React 19) em quatro idiomas, hospedado na Vercel. O conteúdo que muda com frequência (dojos filiados, senseis, eventos, kata, FAQ, artigos) não está no repositório: vem de um CMS Payload pela API REST, com cache por tags.
Quando alguém publica um evento no CMS, a sequência é esta:
- Um hook do CMS faz
POST /api/revalidateno site, comAuthorization: Bearer <segredo>e as tags afetadas (events,event:seminario-nacional-2026). - O site expira o cache dessas tags com
revalidateTage o cache dositemap.xml, dofeed.xmle dollms.txt. - Depois de responder ao CMS, o site envia ao IndexNow as URLs daquele evento nos quatro idiomas.
Sem build, sem deploy. A próxima visita já vê o evento, e o Bing recebe o aviso no mesmo segundo. Dias depois do lançamento, o sitemap já tinha 112 URLs, e cada publicação nova entra nesse fluxo sozinha.
1. O cliente do IndexNow
// src/lib/indexnow.ts
import "server-only";
import { SITE } from "@/lib/site";
export const INDEXNOW_KEY = process.env.INDEXNOW_KEY ?? "";
export async function submitIndexNow(urls: string[]) {
if (!INDEXNOW_KEY || !urls.length) return { skipped: true };
const host = new URL(SITE.url).hostname;
try {
const res = await fetch("https://api.indexnow.org/indexnow", {
method: "POST",
headers: { "Content-Type": "application/json; charset=utf-8" },
body: JSON.stringify({
host,
key: INDEXNOW_KEY,
keyLocation: `${SITE.url}/${INDEXNOW_KEY}.txt`,
urlList: urls.slice(0, 10000),
}),
signal: AbortSignal.timeout(8000),
});
return { ok: res.ok, status: res.status };
} catch {
return { ok: false };
}
}
Três decisões aqui:
- Sem chave, sem envio. Em desenvolvimento e em previews a env não existe, então a função simplesmente não faz nada. Preview nunca deve avisar buscador de URL nenhuma.
- Timeout de 8 segundos e
catchsilencioso. Falha no IndexNow não pode derrubar a revalidação; no pior caso o Bing descobre a página pelo sitemap, como antes. slice(0, 10000)é o limite do protocolo por requisição.
2. A rota de revalidação
// src/app/api/revalidate/route.ts (trecho)
import { timingSafeEqual } from "node:crypto";
import { revalidatePath, revalidateTag } from "next/cache";
import { after, NextResponse } from "next/server";
import { locales } from "@/i18n/routing";
import { submitIndexNow } from "@/lib/indexnow";
import { localizedUrl } from "@/lib/urls";
const SLUG_ROUTES = {
"post:": "/blog/[slug]",
"event:": "/eventos/[slug]",
"dojo:": "/dojos/[slug]",
"person:": "/senseis/[slug]",
"kata:": "/kata/[slug]",
} as const;
function authorized(header: string | null) {
const secret = process.env.REVALIDATE_SECRET;
if (!secret || !header?.startsWith("Bearer ")) return false;
const given = Buffer.from(header.slice(7));
const expected = Buffer.from(secret);
return given.length === expected.length && timingSafeEqual(given, expected);
}
export async function POST(request: Request) {
if (!authorized(request.headers.get("authorization"))) {
return NextResponse.json({ error: "unauthorized" }, { status: 401 });
}
const body = (await request.json().catch(() => ({}))) as { tags?: unknown };
const tags = (Array.isArray(body.tags) ? body.tags : [])
.filter((t): t is string => typeof t === "string");
if (!tags.length) tags.push("posts");
// Next 16 pede o perfil de expiração; { expire: 0 } expira na hora.
for (const tag of tags) revalidateTag(tag, { expire: 0 });
revalidatePath("/sitemap.xml");
revalidatePath("/feed.xml");
// URLs que mudaram, em todos os idiomas, para o IndexNow.
const urls: string[] = [];
for (const tag of tags) {
for (const [prefix, pathname] of Object.entries(SLUG_ROUTES)) {
if (!tag.startsWith(prefix)) continue;
const slug = tag.slice(prefix.length);
for (const locale of locales)
urls.push(localizedUrl(locale, { pathname, params: { slug } }));
}
}
if (urls.length) after(() => submitIndexNow(urls));
return NextResponse.json({ revalidated: true, tags });
}
O detalhe que mais importa é o after(). O webhook do CMS tem o próprio timeout; se a rota esperasse a resposta do IndexNow antes de responder, uma lentidão do lado de lá apareceria como falha do lado de cá. Com after, a resposta sai primeiro e o envio acontece em seguida, ainda dentro da mesma invocação da função.
Outro ponto: as URLs enviadas são as quatro versões de idioma da página. Cada uma é uma URL distinta no índice e cada uma mudou. Quem tem hreflang precisa lembrar disso; enviar só a versão principal deixa as outras três para o rastreamento normal.
A rota ignora tags que não conhece (a lista completa tem um allowlist de prefixos, cortada aqui por espaço) e compara o segredo com timingSafeEqual. A chave do IndexNow é pública por natureza, está num .txt na raiz; o segredo do webhook, não.
3. Servir o arquivo da chave
Essa parte tem um detalhe específico do App Router com i18n. O protocolo quer o arquivo em /<chave>.txt, na raiz. Num projeto com rotas app/[locale]/..., um segmento dinâmico na raiz (app/[key]/route.ts) colide com [locale]. A solução foi um rewrite:
// next.config.ts
async rewrites() {
return [
{
source: "/:key([a-f0-9]{32}).txt",
destination: "/api/indexnow-key?key=:key",
},
];
}
// src/app/api/indexnow-key/route.ts
import { INDEXNOW_KEY } from "@/lib/indexnow";
export function GET(request: Request) {
const key = new URL(request.url).searchParams.get("key");
if (!INDEXNOW_KEY || key !== INDEXNOW_KEY) {
return new Response("Not found", { status: 404 });
}
return new Response(INDEXNOW_KEY, {
headers: {
"Content-Type": "text/plain; charset=utf-8",
"Cache-Control": "public, max-age=86400",
},
});
}
A regex do source só casa 32 caracteres hexadecimais seguidos de .txt, então nenhuma outra URL do site passa por ali. E a rota só responde se a chave da URL for exatamente a da env: trocar a chave na Vercel e fazer deploy é o suficiente para rotacionar.
Por que não um arquivo estático em public/? Porque a chave viraria parte do repositório, e eu queria poder trocá-la sem commit. É gosto; o arquivo estático funciona igual.
O que observar depois
- Bing Webmaster Tools. Cadastre o site (dá para importar a verificação do Google Search Console em um clique). Há um relatório de IndexNow mostrando cada URL recebida e o que o Bing fez com ela. É ali que você confirma que o fluxo funciona de ponta a ponta.
- Logs. Guarde o status de cada envio.
202nos primeiros envios é normal (a chave ainda está sendo validada).403recorrente é arquivo de chave errado ou inacessível.422é URL de outro host, o que acontece quandoSITE_URLestá apontando para um preview. - Não reenvie o que não mudou. O protocolo é explícito: mandar a mesma URL repetidamente sem alteração é tratado como spam (
429). Envie no evento de mudança, não num cron.
Se você não quer escrever código
- Cloudflare: a opção Crawler Hints, gratuita, faz o envio sozinha para zonas com proxy ativo, com base no que a própria Cloudflare vê mudar no cache. (Não serve para quem usa a Cloudflare só como DNS, como no caso acima, onde os registros ficam em modo DNS only por causa da Vercel.)
- WordPress: o plugin oficial da Bing, o Rank Math e o AIOSEO enviam IndexNow ao publicar; no Yoast, é recurso da versão Premium.
- Bing Webmaster Tools: aceita envio manual de URLs e tem API própria, útil para testar antes de automatizar.
- Qualquer stack: um
curlno hook de publicação resolve. O protocolo é só HTTP.
curl -X POST https://api.indexnow.org/indexnow \
-H "Content-Type: application/json; charset=utf-8" \
-d '{"host":"seudominio.com","key":"SUACHAVE","urlList":["https://seudominio.com/nova-pagina"]}'
Checklist
- Gere uma chave (
openssl rand -hex 16dá 32 caracteres hexadecimais) e sirva em/<chave>.txt. - Encontre o ponto do seu sistema que já sabe que uma página mudou: hook do CMS, webhook de deploy,
afterSavedo modelo. - Nesse ponto, monte a lista de URLs completas e canônicas que mudaram, incluindo versões de idioma.
- Faça o POST depois de responder ao chamador, com timeout curto e sem lançar exceção.
- Pule o envio quando não houver chave (dev, preview, testes).
- Cadastre o site no Bing Webmaster Tools e confira o relatório de IndexNow nos primeiros dias.
- Continue cuidando do sitemap e do Search Console: o Google segue sendo pull.
Fechando
IndexNow não é truque de SEO. É a parte do sistema que avisa quem precisa saber que algo mudou, do mesmo jeito que um webhook avisa um serviço interno. Para um site que vive de conteúdo que muda (eventos, diretório, notícias), a diferença entre "o Bing sabe em um minuto" e "o Bing sabe quando passar por aqui" é grande, e o preço é uma função de vinte linhas e um arquivo de texto.
Se você já implementou em outra stack (Laravel, Rails, Django, Astro com SSR), me interessa saber onde você pendurou o envio e o que viu no painel do Bing nas primeiras semanas.