DEV Community

Najmul Hasan
Najmul Hasan

Posted on Originally published at najmulhasan.dev

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

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
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 };
}
Enter fullscreen mode Exit fullscreen mode

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 });
}
Enter fullscreen mode Exit fullscreen mode

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"]}'
Enter fullscreen mode Exit fullscreen mode

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 [email protected].

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


Originally published at najmulhasan.dev/blog/indexnow-nextjs-bing-yandex.

Top comments (1)

Collapse
 
mohammad_zanjani profile image
Mohammad Zanjani •

Great writeup on IndexNow! In production at Rubinesh, we combine IndexNow API triggers inside Next.js Route Handlers with On-Demand Revalidation (revalidatePath). That way, whenever CMS content updates, both the ISR cache and the search engine index update synchronously without waiting for cron crawlers.