Articles /
IndexNow in Next.js: Notify Bing and Yandex on Publish (Google Is Different)
IndexNow in Next.js: generate a key, host the key file, send URLs from a route handler, read 200/202/403/422/429 and see why Google is not covered.
My blog commits each article to GitHub and Vercel redeploys. Bing and Yandex then find the new URL whenever they next crawl. IndexNow in Next.js shortens that wait: you send the URL to one endpoint the moment it changes. This article covers the key file, a protected route handler, the response codes, and why Google is not covered. Everything targets Next.js 16 (App Router) on Node.js 20.9 or newer.
The short answer
Host a key file at your site root, then POST the changed URLs with that key to https://api.indexnow.org/indexnow. According to the IndexNow documentation, participating engines share submitted URLs with each other, so one request is enough. As of mid-2026, Google is not one of them.
{
"host": "najmulhasan.dev",
"key": "<your key>",
"keyLocation": "https://najmulhasan.dev/<your key>.txt",
"urlList": ["https://najmulhasan.dev/blog/<slug>"]
}
Step 1: Create the IndexNow key and key file
The key must be 8 to 128 characters: letters, digits and dashes. The file is named after the key and contains only the key.
KEY=$(openssl rand -hex 16) # 32 hex characters
printf '%s' "$KEY" > "public/$KEY.txt"
echo "$KEY"
Store the same value as INDEXNOW_KEY in Vercel and in .env.local. After the next deploy, https://najmulhasan.dev/<key>.txt must open in a browser and show only the key.
Put the file at the root. The docs allow a deeper location, but then the key only covers URLs under that path, and they strongly recommend the root.
Step 2: Send URLs from a Next.js route handler
Two files. A second variable, INDEXNOW_SECRET, protects your own endpoint so strangers cannot make your site send requests to IndexNow.
// lib/indexnow.ts (Next.js 16, App Router, Node.js 20.9+)
const HOST = "najmulhasan.dev";
const ENDPOINT = "https://api.indexnow.org/indexnow";
export function isOwnUrl(url: unknown): url is string {
return typeof url === "string" && url.startsWith(`https://${HOST}/`);
}
export async function submitToIndexNow(urls: string[]) {
const key = process.env.INDEXNOW_KEY;
if (!key) throw new Error("INDEXNOW_KEY is not set");
const res = await fetch(ENDPOINT, {
method: "POST",
headers: { "Content-Type": "application/json; charset=utf-8" },
body: JSON.stringify({
host: HOST,
key,
keyLocation: `https://${HOST}/${key}.txt`,
urlList: [...new Set(urls)],
}),
});
return { status: res.status, accepted: res.status === 200 || res.status === 202 };
}
// app/api/indexnow/route.ts
import { timingSafeEqual } from "node:crypto";
import { isOwnUrl, submitToIndexNow } from "@/lib/indexnow";
function authorized(request: Request): boolean {
const secret = process.env.INDEXNOW_SECRET;
if (!secret) return false;
const sent = Buffer.from(request.headers.get("authorization") ?? "");
const expected = Buffer.from(`Bearer ${secret}`);
return sent.length === expected.length && timingSafeEqual(sent, expected);
}
export async function POST(request: Request) {
if (!authorized(request)) {
return Response.json({ error: "unauthorized" }, { status: 401 });
}
const body = await request.json().catch(() => null);
const urls = body?.urls;
if (!Array.isArray(urls) || urls.length === 0 || !urls.every(isOwnUrl)) {
return Response.json(
{ error: "urls must be a non-empty array of URLs on this host" },
{ status: 400 },
);
}
try {
const result = await submitToIndexNow(urls);
return Response.json(result, { status: result.accepted ? 200 : 502 });
} catch {
return Response.json({ error: "submission failed" }, { status: 500 });
}
}
The host filter exists because IndexNow answers 422 for URLs that do not belong to the host. The Set removes duplicates. One POST can carry up to 10,000 URLs.
Submit URLs that were added, updated or deleted. That is what the protocol is for.
Step 3: Test it and read the response codes
# 1. the key file must be public and contain only the key
curl -i "https://najmulhasan.dev/$INDEXNOW_KEY.txt"
# 2. submit one URL through your route
curl -i -X POST https://najmulhasan.dev/api/indexnow \
-H "Authorization: Bearer $INDEXNOW_SECRET" \
-H "Content-Type: application/json" \
-d '{"urls":["https://najmulhasan.dev/blog/nextjs-rate-limiting-upstash-redis-serverless"]}'
The response codes, as the documentation lists them:
- 200: URL submitted successfully.
- 202: received, key validation pending.
- 400: invalid format.
- 403: key not valid (file not found, or the key is not in the file).
- 422: URLs do not belong to the host, or the key does not match the schema.
- 429: too many requests (potential spam).
Why IndexNow does not cover Google
As of mid-2026, Google does not support IndexNow, although a test was announced in October 2021. A third-party summary from June 2026 lists Bing, Yandex, Naver, Seznam and Yep as participants. That list can change, so verify it before relying on it.
For Google, keep your sitemap and use "Request indexing" in Search Console. That step is manual, because Google has no indexing API for normal pages. IndexNow only delivers the news that a URL changed. Ranking is a separate question, and I cannot tell you what any engine will rank.
Pitfalls
- A 200 means received, not indexed. The documentation says the same.
- Submit after the deploy is live. If you call the route when you push the commit, Vercel may still be building and the crawler gets a 404. Trigger it from a webhook or CI step that runs once the deployment has succeeded.
- Only submit changes. Resubmitting unchanged URLs is how you earn a 429.
- The key file is public. Treat the key as an identifier, not a secret, and protect your own endpoint with a separate value like
INDEXNOW_SECRET. - What I have and have not tested. I added this to my blog's admin and tested it against a mock IndexNow server. I have not run it against the live service yet, so I make no claim about how fast any engine picks up a URL.
Summary
- IndexNow needs one key file and one POST, and a single submission reaches the participating engines.
- Protect your route handler with its own secret and filter URLs to your host.
- Submit changed URLs only, and only after the deploy is live.
- Google is not covered, so keep the sitemap and Search Console.
Next: protect routes like this one from abuse with per-IP limits in Next.js rate limiting on Vercel with Upstash Redis.