next-indexnow
v1.0.0
Published
IndexNow protocol integration for Next.js applications
Downloads
216
Maintainers
Readme
next-indexnow
A simple, lightweight, modern package to integrate the IndexNow protocol into your Next.js applications — notify search engines (Bing, Yandex, Seznam, and more) instantly when your content changes.
Developed and maintained by Prime Sentia.
What is IndexNow?
IndexNow is an open protocol created by Microsoft and Yandex that allows websites to easily notify search engines whenever their website content is created, updated, or deleted.
Without IndexNow, search engines can take days or weeks to discover that your content has changed because they rely on slow, scheduled web crawling. With IndexNow, you instantly "ping" search engines to let them know a URL has changed, prompting them to quickly index the fresh content.
Supported Search Engines
IndexNow shares submitted URLs across all participating search engines simultaneously. Submitting to one endpoint automatically notifies all of the following engines:
- Microsoft Bing (and by extension, Bing Chat, Copilot, and Yahoo)
- Yandex
- Seznam.cz
- Naver
- Yep
(Note: Google is not currently participating in the IndexNow protocol, but fast indexing on Bing/Copilot is crucial for modern AI-driven search visibility).
Why it matters for AI search
AI assistants (Bing Copilot, ChatGPT search, and others) increasingly answer from freshly-indexed content. Because IndexNow pings participating engines within seconds instead of waiting days for a crawl, your latest content is far more likely to be picked up — the foundation of Generative Engine Optimization (GEO).
Installation
npm install next-indexnow
# or
yarn add next-indexnow
# or
pnpm add next-indexnowAutomatic Setup (Recommended)
IndexNow requires you to prove ownership of your site by serving a text file containing your API key at the root of your domain. We've completely automated this process.
Just run the following command at the root of your Next.js project:
npx next-indexnow initWhat this does:
- Generates a secure, 32-character API key.
- Creates the verification file automatically in your
public/folder (e.g.public/a1b2c3d4...txt). Next.js will naturally serve this at your root domain. - Injects
INDEXNOW_KEY=...into your.env.localor.envfile so you can access it in your code.
That's it! You are fully configured and ready to notify search engines.
(If you prefer to configure everything manually, you can generate a key using import { generateKey } from 'next-indexnow', save it to your .env manually, and serve the .txt file using Next.js Route Handlers.)
Usage
You can trigger IndexNow notifications whenever your content changes (e.g., in a webhook from your CMS, in a Server Action, or a custom API route).
Single URL Notification
import { notifyUrl } from 'next-indexnow';
async function publishArticle(slug) {
// ... your database logic ...
// Notify IndexNow
const articleUrl = `https://yoursite.com/blog/${slug}`;
await notifyUrl(articleUrl, {
key: process.env.INDEXNOW_KEY,
});
console.log('IndexNow notified!');
}Batch URLs Notification
If you update multiple pages at once, or want to send your entire sitemap, you can batch up to 10,000 URLs in a single request.
import { notifyBatch } from 'next-indexnow';
async function syncAllPages() {
const urls = [
'https://yoursite.com/page-1',
'https://yoursite.com/page-2',
// ... up to 10,000 URLs
];
await notifyBatch(urls, {
key: process.env.INDEXNOW_KEY,
host: 'yoursite.com', // Required for batch notifications
});
}Automatic Sitemap Submission (Killer Feature 🚀)
Instead of manually keeping track of URLs, you can point next-indexnow directly to your XML sitemap. It will automatically fetch the sitemap, extract all URLs (with zero heavy XML dependencies), de-duplicate them, split them into chunks of 10,000 (IndexNow's limit), and submit everything at once.
It transparently handles sitemap-index files (recursing into child sitemaps), gzip-compressed sitemaps (.xml.gz), CDATA-wrapped entries and multi-line <loc> values — so it works with the output of next-sitemap and the App Router's generateSitemaps().
import { notifySitemap } from 'next-indexnow';
async function syncEntireWebsite() {
const result = await notifySitemap('https://yoursite.com/sitemap.xml', {
key: process.env.INDEXNOW_KEY,
host: 'yoursite.com', // Required
});
console.log(`Submitted ${result.submitted} URLs (skipped ${result.skipped}).`);
}The result object
notifyUrl, notifyBatch and notifySitemap all resolve to a structured IndexNowResult:
interface IndexNowResult {
ok: boolean; // every request returned 2xx
submitted: number; // URLs actually sent
skipped: number; // duplicates / off-host URLs
responses: {
ok: boolean;
status: number;
statusText: string;
urls: string[];
attempts: number;
}[];
}Note that IndexNow returns 202 Accepted ("validation pending") for valid submissions — this is treated as success. On a non-retryable failure (e.g. 403 invalid key, 422 host mismatch) a typed IndexNowError is thrown, carrying status, statusText, retryable and the raw responseBody.
Next.js helpers (next-indexnow/next)
Fire-and-forget after the response: notifyAfter
Calling await notifyUrl(...) inside a Server Action blocks the response on a network round-trip. notifyAfter schedules the ping with Next.js's after() so it runs after the response is sent, and never surfaces indexing errors to the user.
import { notifyAfter } from 'next-indexnow/next';
export async function publishPost(slug: string) {
// ...persist your changes...
await notifyAfter(`https://yoursite.com/blog/${slug}`, {
key: process.env.INDEXNOW_KEY!,
});
}Revalidate + notify in one call: revalidateAndNotify
On-demand revalidation is the canonical "this content changed, re-index it" signal. revalidateAndNotify revalidates the path (or tag) and then notifies IndexNow about the absolute URL.
import { revalidateAndNotify } from 'next-indexnow/next';
await revalidateAndNotify('/blog/my-post', {
baseUrl: 'https://yoursite.com',
key: process.env.INDEXNOW_KEY!,
// tag: 'posts', // optionally revalidate a cache tag instead of a path
});
notifyAfterresolves as soon as the work is scheduled — it does not wait for the IndexNow round-trip to finish.
Pages Router key file: createIndexNowApiHandler
If you use the Pages Router, serve the key file from pages/api/[key].ts (add a rewrite so it's reachable at the domain root):
import { createIndexNowApiHandler } from 'next-indexnow/next';
export default createIndexNowApiHandler(process.env.INDEXNOW_KEY!);Verifying your key file
A missing or mismatched key file is the #1 cause of 403/422 rejections. After deploying, confirm it's live:
import { verifyKeyFile } from 'next-indexnow';
const result = await verifyKeyFile({ key: process.env.INDEXNOW_KEY!, host: 'yoursite.com' });
if (!result.ok) {
console.error(`Key file check failed at ${result.url}: ${result.reason}`);
}Or from the command line: npx next-indexnow verify (reads INDEXNOW_HOST), or pass the full URL: npx next-indexnow verify https://yoursite.com/<key>.txt.
CLI
npx next-indexnow init # generate a key, write public/<key>.txt, update .env
npx next-indexnow submit <url> [url2…] # submit one or more URLs (reads INDEXNOW_KEY)
npx next-indexnow sitemap <url> # submit every URL in a sitemap (or sitemap index)
npx next-indexnow verify [keyLocation] # check the key file is live (reads INDEXNOW_HOST)The submit/sitemap/verify commands read INDEXNOW_KEY (and optionally INDEXNOW_HOST) from the environment or your .env.local/.env. This makes next-indexnow sitemap https://yoursite.com/sitemap.xml ideal as a postbuild step in CI.
Options
All notify functions accept an options object:
key(string, required): Your IndexNow API key. Validated locally before any request.host(string, required for batch/sitemap): Your website's host (e.g.,www.example.com).wwwand non-wwwcount as different hosts.keyLocation(string, optional): Full URL to your key file if it's not hosted at the exact root with the exact key name.endpoint(string, optional): A known engine name ('indexnow'|'bing'|'yandex'|'seznam'|'naver'|'yep') or a raw host. Defaults to'indexnow'(api.indexnow.org), which already propagates to every participating engine within ~10s — so per-engine targeting is rarely necessary.maxRetries(number, optional): Retry attempts for transient failures (429/5xx). Defaults to3; honors theRetry-Afterheader.retryDelayMs(number, optional): Base delay for exponential backoff between retries. Defaults to1000.timeoutMs(number, optional): Per-request timeout viaAbortController. Defaults to10000; set to0to disable. A timeout is not retried (so worst-case blocking stays bounded), but other transient network errors are.onHostMismatch('throw' | 'skip', optional): What to do with URLs that don't belong tohost. Defaults to'throw'; use'skip'to drop them and count them inresult.skipped.since(Date, optional, sitemap only): Only submit URLs whose<lastmod>is at or after this date (child sitemaps older thansinceare skipped without being fetched). URLs without a<lastmod>are always included. Great for incremental cron runs. A date-only<lastmod>(YYYY-MM-DD) is treated as end of that UTC day, and a datetime without a timezone is interpreted as UTC, so filtering is deterministic across deployments.
Runtime: requires Node 20+ (for the Web Crypto global used by
generateKey), the Edge runtime, or a browser. The core notify functions are dependency-free and Edge-compatible.
Targeting a specific search engine
Submitting once to the default endpoint already shares your URLs with every participating engine, but you can target one directly by name (or import the SEARCH_ENGINES map):
import { notifyUrl, SEARCH_ENGINES } from 'next-indexnow';
await notifyUrl('https://yoursite.com/page', { key, endpoint: 'bing' });
// SEARCH_ENGINES === { indexnow, bing, yandex, seznam, naver, yep }Example
A minimal App Router wiring (key file, a notify Server Action, sitemap + postbuild submission) lives in examples/app-router.
Contributing
This is an open-source project by Prime Sentia, and we welcome contributions from the community!
If you'd like to help improve next-indexnow:
- Fork the repository.
- Create a new branch (
git checkout -b feature/my-amazing-feature). - Make your changes and commit them (
git commit -m 'feat: add my amazing feature'). - Push to the branch (
git push origin feature/my-amazing-feature). - Open a Pull Request.
If you find a bug or have a feature request, please open an issue.
License
MIT
Built and maintained by Prime Sentia.
