nextjs-indexnow
v0.1.4
Published
IndexNow integration for Next.js — submit added and changed URLs to Bing, Yandex, and other IndexNow search engines after every build.
Downloads
96
Maintainers
Readme
nextjs-indexnow
IndexNow integration for Next.js — automatically ping Bing, Yandex, Seznam, Naver, and Yep with URLs that were added or changed in the latest build.
- Zero sitemap dependency. Discovers URLs directly from Next's build output (
.next/prerender-manifest.json+.next/routes-manifest.json), including every result ofgenerateStaticParams. - Diff-based by default. Persists a state file between builds and only submits URLs that are new or whose content fingerprint changed.
- Bundler-agnostic. Runs as a
postbuildCLI, so it works with both webpack and Turbopack (Next 15/16). - Safe defaults. Dry-run unless
INDEXNOW_SUBMIT=true. - Pluggable state. Ship your own adapter for Vercel KV, Redis, S3, etc.
Installation
pnpm add -D nextjs-indexnow
# or: npm i -D nextjs-indexnow / yarn add -D nextjs-indexnowQuickstart
1. Generate a key and host the key file:
npx nextjs-indexnow init
# writes public/<key>.txt and prints the key2. Wire up next.config.ts:
import { withIndexNow } from "nextjs-indexnow";
export default withIndexNow(
{
// your existing Next config
},
{
host: "example.com",
exclude: ["/api/**", "/admin/**"],
},
);3. Add a postbuild script:
// package.json
{
"scripts": {
"build": "next build",
"postbuild": "nextjs-indexnow"
}
}4. Configure env vars:
| Var | Required | Description |
| ----------------- | -------- | --------------------------------------------------------------- |
| INDEXNOW_KEY | yes | The key you generated above. Must match public/<key>.txt. |
| INDEXNOW_SUBMIT | no | Set to "true" on production deploys to actually POST. Default: dry-run. |
Set INDEXNOW_SUBMIT=true only in production — preview deploys should stay in dry-run to avoid flooding the IndexNow API with short-lived URLs.
How URL discovery works
After next build, nextjs-indexnow reads:
.next/prerender-manifest.json→ concrete URLs from static pages andgenerateStaticParams..next/routes-manifest.json→ additional static routes,basePath, and redirects (to strip redirect sources).
It then filters:
- Internal routes (
/_error,/_not-found,.rscendpoints). - Anything under
/api/*. - URLs matched by your
redirects()config. - Your
excludeglobs.
…and keeps only URLs matched by your include globs (default: everything).
Each URL is fingerprinted from manifest metadata plus the generated .html file's size and mtime. On the next build, URLs whose fingerprint hasn't changed are skipped.
Config reference
interface IndexNowOptions {
/** Bare hostname, e.g. "example.com". Required. */
host: string;
/** Env var holding the IndexNow key. Default "INDEXNOW_KEY". */
keyEnv?: string;
/** Public URL where the key is served. Default: https://{host}/{key}.txt. */
keyLocation?: string;
/** Allowlist globs. Default ["/**"]. */
include?: string[];
/** Blocklist globs. Default []. */
exclude?: string[];
/** Pluggable state storage. Default FileStateAdapter(".indexnow-state.json"). */
state?: StateAdapter;
/** IndexNow endpoint URL. Override for testing. */
endpoint?: string;
/** Env var flag that must equal "true" to POST. Default "INDEXNOW_SUBMIT". */
submitEnv?: string;
/** .next directory. Default ".next". */
distDir?: string;
}Custom state adapters
Ship state somewhere persistent — Vercel KV, S3, Redis, Postgres — by implementing:
interface StateAdapter {
read(): Promise<IndexNowState | null>;
write(state: IndexNowState): Promise<void>;
}Example (Vercel KV):
import { kv } from "@vercel/kv";
import { withIndexNow, type IndexNowState, type StateAdapter } from "nextjs-indexnow";
const kvState: StateAdapter = {
async read() {
return (await kv.get<IndexNowState>("indexnow:state")) ?? null;
},
async write(state) {
await kv.set("indexnow:state", state);
},
};
export default withIndexNow(
{ /* next config */ },
{ host: "example.com", state: kvState },
);CLI
nextjs-indexnow [run] Read .next and submit changed URLs (run is default)
nextjs-indexnow init Generate a key and write public/<key>.txt
nextjs-indexnow --helpHow config is resolved
withIndexNow writes your options to a sidecar file at node_modules/.cache/nextjs-indexnow/options.json the moment Next imports your config. The CLI reads that file after the build. This means:
- You can use
next.config.ts,.mjs,.js, or.cjs— we don't parse your config, so whatever Next loads works. - The function returns your Next config unchanged, so Next's config validator won't warn about unknown keys.
- The sidecar lives under
node_modules/.cache, which is already gitignored everywhere.
If you can't use withIndexNow — e.g. a setup where options need to live outside next.config — drop a standalone indexnow.config.mjs next to your project root:
// indexnow.config.mjs
/** @type {import('nextjs-indexnow').IndexNowOptions} */
export default {
host: "example.com",
exclude: ["/api/**"],
};The Next-build JSON takes precedence; the standalone file is the fallback.
Why a postbuild CLI and not a build hook?
Next 16 defaults to Turbopack, which silently ignores next.config's webpack customization. A webpack-hook plugin would no-op for most users with no warning. A postbuild script runs once after every build, regardless of bundler.
License
MIT © Sebastian Grebe
