Sebastian Grebe
HomeExperienceProjectsBlogLabContact
Back to blog
April 17, 2026·Open Source

nextjs-indexnow — Ping Bing and Yandex Every Time You Deploy a Next.js Site

I published an open-source Next.js plugin that submits new and changed URLs to IndexNow after every build. Zero sitemap dependency, Turbopack-safe, diff-based. Here's why it exists and how it works.

Sebastian Grebe

Sebastian Grebe

April 17, 2026

nextjs-indexnow — Ping Bing and Yandex Every Time You Deploy a Next.js Site

Google will eventually crawl your new blog post. Bing won’t — not for days, sometimes weeks. Yandex is worse. Seznam, Naver, and Yep are somewhere in the “maybe never” range unless you push them.

IndexNow fixes that. It’s an open protocol where you POST a list of URLs to one endpoint, and every participating search engine refetches them within minutes. Microsoft backs it. Yandex backs it. It’s boring, it works, and almost nobody does it — because wiring it into a real site is more annoying than it should be.

I shipped nextjs-indexnow this week: a Next.js plugin that handles the whole thing automatically after every build. Zero sitemap dependency. Diff-based so you’re not spamming the endpoint. Works with Turbopack (which is the part most existing tools quietly get wrong).

This post is about why that last part matters, and what I had to figure out to make the plugin reliable across both Next 15 and Next 16.

What IndexNow actually is

The protocol is trivial on paper:

  1. You generate a random key.
  2. You host it at https://yourdomain.com/{key}.txt.
  3. You POST a JSON body with your host, your key, and the URLs you want recrawled.

Something like:

{
  "host": "example.com",
  "key": "24484e41aeaea0fb94bef0a47575be48",
  "keyLocation": "https://example.com/24484e41aeaea0fb94bef0a47575be48.txt",
  "urlList": ["https://example.com/blog/new-post"]
}

The endpoint is https://api.indexnow.org/indexnow. It fans out to every participating engine. You get back a 200 or a 202, you’re done.

The hard part isn’t the protocol. The hard part is knowing which URLs to send and when, without pinging the same URLs every deploy forever.

Why I didn’t just use an existing library

I almost did. The top npm candidate, indexnow-submitter, has a sitemap parser and a caching layer. But:

  • Under 100 weekly downloads.
  • Solo maintainer, no real community.
  • Sitemap-driven — which means your IndexNow submissions depend on your sitemap being accurate, and drift between the two is a silent failure mode.

I looked at whether next-sitemap bundled IndexNow support. It doesn’t. No official Microsoft or Bing Node package exists. No Vercel integration.

The actual submission is maybe 30 lines of code. The diff logic — “what changed since last deploy?” — is where you’d want the library to earn its keep, and that part has to know your content model. A generic library can’t. So I wrote my own.

The Turbopack problem

Here’s the gotcha that sunk my first design.

My instinct was to ship a classic Next.js plugin: wrap the user’s config with withIndexNow(), tap into a webpack hook, run the submission at the end of the build. Clean, idiomatic, one file to add.

Turbopack silently skips next.config’s webpack() function. Next 16 defaults to Turbopack. So a plugin that relies on a webpack hook runs zero code for most users, with no warning. It would pass your tests, silently do nothing in production, and you’d find out three weeks later when Bing still hasn’t indexed your new pages.

The fix is to not be a build-time plugin at all. nextjs-indexnow ships as a CLI you run from postbuild:

{
  "scripts": {
    "build": "next build",
    "postbuild": "nextjs-indexnow"
  }
}

Bundler-agnostic. Fires exactly once per build. Works identically on webpack and Turbopack. This is also how next-sitemap ships — they hit the same wall years ago.

I still export withIndexNow(nextConfig, { host: "..." }) as a config wrapper, but its only job is to attach your options to the config so the CLI can read them back. No hook, no magic.

URL discovery without depending on sitemap

The other design question: how does the CLI know what URLs your site publishes?

Most tools parse sitemap.xml. That couples you to whichever sitemap generator you use, and if your sitemap is wrong, your IndexNow submissions are wrong too.

There’s a better source: Next writes the complete URL list to its own build manifests.

  • .next/prerender-manifest.json has every concrete URL, including every result of generateStaticParams. Blog posts, docs pages, dynamic routes — they’re all there, keyed by their real URL, not the [slug] template.
  • .next/routes-manifest.json adds static routes, basePath, and redirects.

Union them, filter out internals (/_not-found, *.rsc, /api/*), strip URLs matched by your redirects() config, apply user include/exclude globs, and you’ve got the same list your sitemap would have — derived straight from the authoritative source.

On my own site this worked first try. 45 URLs, matching exactly what I’d expect, with no sitemap plumbing needed.

Diff-based submission so you don’t spam the endpoint

The IndexNow spec says you can submit whatever you want, whenever you want. But submitting 45 URLs on every deploy — including the 42 that haven’t changed since last week — is noise. Search engines throttle noisy senders. You want to tell them what’s new.

The plugin fingerprints each URL from its manifest metadata plus the generated HTML file’s size and mtime, stores those fingerprints in a local .indexnow-state.json, and on the next build diffs against it:

  • URLs not in state → submit (new).
  • URLs with a different fingerprint → submit (changed).
  • URLs unchanged → skip.

After the first build, typical deploys submit 0 to 3 URLs — the ones you actually touched.

State is pluggable. Default is a local JSON file. If you want Vercel KV or S3, implement a two-method adapter and pass it in:

interface StateAdapter {
  read(): Promise<IndexNowState | null>;
  write(state: IndexNowState): Promise<void>;
}

Safe by default

One design choice I feel strongly about: the plugin is dry-run unless you explicitly opt in.

INDEXNOW_SUBMIT=true

If that env var isn’t true, the CLI logs what it would submit and exits cleanly. No POST, no state file update, nothing external happens.

This means you can install the plugin and run pnpm build locally without accidentally pinging production URLs that aren’t deployed yet. Preview builds on Vercel stay quiet. Only your production environment flips the flag.

The first time I ran it against my own site in production, this mattered — I caught one include glob I’d written wrong before it hit the endpoint. Free safety net.

What went into the box

  • withIndexNow() config wrapper with host validation.
  • nextjs-indexnow CLI for postbuild, plus nextjs-indexnow init to generate your key and write public/{key}.txt.
  • URL discovery from Next’s build manifests. Zero sitemap dependency.
  • Diff-based submission with pluggable state.
  • 34 unit tests covering discovery, diffing, submission batching, state round-trips.
  • TypeScript types, ESM + CJS bundles.
  • MIT licensed, published to npm, source on GitHub.

Full install and config reference lives in the README on GitHub.

Who this is for

If you’re running a content-heavy Next.js site — blog, docs, marketing pages — and you care about indexing speed on anything other than Google, this is the least annoying way I’ve found to wire it up. Add one line to next.config, one line to package.json, one env var on Vercel. That’s the whole integration.

If you’re building agents or tooling on top of Next.js and want a reference for how to do “run after build” correctly in a post-Turbopack world, the source is 400 lines and might save you the webpack-hook detour I almost shipped.

Grab it from the Lab or straight from npm. If something breaks for you, open an issue — I’m actively using this on my own sites, so feedback gets fixed fast.