Articles /

IndexNow in Next.js: Submit New Pages to Bing and Yandex (Google Is Different)

IndexNow in Next.js 16: host the key file, POST changed URLs to api.indexnow.org, read the response codes, and see why Google needs Search Console instead.

By Najmul Hasan · · 5 min read

When you publish a page, Bing and Yandex normally find it whenever their crawler gets around to it. IndexNow lets your site tell them immediately: one POST request lists the URLs that were added, changed or deleted. This guide sets up IndexNow in Next.js 16 (App Router): the key file, a submit function, a protected route handler and the response codes. It also covers the part many guides blur: Google does not take part in IndexNow.

The short answer

Host a text file named <key>.txt at the root of your site, containing the key. Then POST JSON to https://api.indexnow.org/indexnow once the page is live. According to indexnow.org, a notification sent to one participating engine is shared with the others. Those engines are Bing, Yandex, Naver, Seznam and Yep. Google is not one of them, so for Google you still rely on your sitemap and Search Console.

publish → page is live (200) → POST changed URLs to IndexNow → Bing, Yandex and partners are notified

1. Create a key and host the key file

The key must be 8 to 128 characters. I use a 32-character hex string:

KEY=$(node -p "require('node:crypto').randomUUID().replaceAll('-', '')")
printf '%s' "$KEY" > "public/$KEY.txt"
echo "INDEXNOW_KEY=$KEY"

Next.js serves everything in public/ from the site root, so the file appears at https://www.example.com/<key>.txt. Add INDEXNOW_KEY and SITE_URL (your canonical origin, for example https://www.example.com) to your environment variables. Check the file is reachable before you submit anything:

curl -i https://www.example.com/<key>.txt

The body must be exactly the key. The key is not a secret in practice, because anyone can fetch the file. It only proves you control the host.

2. A small submit function

Next.js 16, TypeScript, plain fetch, server only:

// lib/indexnow.ts
const ORIGIN = process.env.SITE_URL!; // e.g. "https://www.example.com"
const KEY = process.env.INDEXNOW_KEY!;

export async function submitIndexNow(paths: string[]) {
  const host = new URL(ORIGIN).host;
  const urlList = paths.map((p) => new URL(p, ORIGIN).toString());

  const res = await fetch("https://api.indexnow.org/indexnow", {
    method: "POST",
    headers: { "Content-Type": "application/json; charset=utf-8" },
    body: JSON.stringify({ host, key: KEY, keyLocation: `${ORIGIN}/${KEY}.txt`, urlList }),
    cache: "no-store",
    signal: AbortSignal.timeout(8000),
  });

  return { ok: res.status === 200 || res.status === 202, status: res.status, submitted: urlList.length };
}

Every URL must belong to host. One POST can carry up to 10,000 URLs, far more than a blog needs.

3. A route handler you can call after publishing

This route triggers outbound requests under your identity, so it needs the same auth as the rest of your admin:

// app/api/admin/indexnow/route.ts
import { isAdmin, sameOrigin } from "@/lib/admin-auth"; // your existing admin auth helpers
import { submitIndexNow } from "@/lib/indexnow";

export async function POST(req: Request) {
  if (!isAdmin(req) || !sameOrigin(req)) return new Response("Forbidden", { status: 403 });

  const { slugs } = (await req.json()) as { slugs?: string[] };
  const valid =
    Array.isArray(slugs) &&
    slugs.length >= 1 &&
    slugs.length <= 20 &&
    slugs.every((s) => /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(s));
  if (!valid) return Response.json({ error: "Send 1 to 20 valid slugs" }, { status: 400 });

  const result = await submitIndexNow(slugs.map((s) => `/blog/${s}`));
  return Response.json(result, { status: result.ok ? 200 : 502 });
}

The slug check means the caller can only submit URLs under /blog/. Throttle this route too; the pattern is in my Upstash rate limiting guide

4. Submit after the page is live

Order matters. Search engines fetch your key file and the URL after they receive the ping. If the deploy is still running, they may see a 404. My admin commits the article, waits until the article URL returns 200, then calls the route above. Submit new, changed and deleted URLs only, not your whole site on every deploy.

5. Read the response

From the IndexNow documentation:

  • 200: submitted successfully.
  • 202: received, key validation still pending.
  • 400: invalid format.
  • 403: key not valid (file missing, or the key is not in the file).
  • 422: URLs don't belong to the host, or the key doesn't match the protocol schema.
  • 429: too many requests.

You can test by hand before wiring any code:

curl -i -X POST https://api.indexnow.org/indexnow \
  -H 'Content-Type: application/json; charset=utf-8' \
  -d '{"host":"www.example.com","key":"<key>","urlList":["https://www.example.com/blog/my-post"]}'

6. What about Google?

Google does not support IndexNow (as of October 2026, verify). For Google, submit your sitemap in Search Console, link new posts from pages Google already crawls, and use "Request indexing" in the URL Inspection tool for important pages. That button is manual; there is no indexing API for normal pages. Treat the result as weeks to months, not minutes.

Pitfalls

  • A ping is a request, not a promise. Engines still decide whether a page deserves to be indexed.
  • Host must match exactly. www.example.com and example.com are different hosts. Submit the canonical one, or you get a 422.
  • The key file must stay reachable. A redirect, rewrite or auth gate in front of /<key>.txt causes 403s.
  • Don't ping on draft saves. Submit when the page is public.
  • Don't resubmit unchanged URLs on a loop. It adds noise and gains nothing.
  • Protect the route. Without auth it is an open relay to a third-party API.

What I tested

The IndexNow call in my admin passed checks against a mock IndexNow server, along with the rest of the admin's 41 API checks. I haven't run it against the live endpoint, so I'm not claiming any indexing speed. Run a single URL by hand with the curl command above first.

Summary

  • Host <key>.txt in public/ and POST changed URLs to api.indexnow.org/indexnow.
  • Submit only after the page returns 200, and only URLs on your canonical host.
  • Bing, Yandex, Naver, Seznam and Yep are covered. Google is not.
  • For Google, use a sitemap, Search Console and internal links.

Need this built or fixed in your project? I do it as a fixed-price job. Email hire@najmulhasan.dev.

Next: Supabase email OTP in Next.js with 6-digit codes.