← All notes

Adding IndexNow to My Next.js Portfolio

How I wired IndexNow into my portfolio without turning every deploy or page view into an indexing request.

7 min readBy Jansen Cadorna
  • seo
  • nextjs
  • indexnow
  • engineering
Cover image for Adding IndexNow to My Next.js Portfolio

I recently spent time making my portfolio easier for search engines to understand.

That started with the usual foundation: canonical URLs, a sitemap, structured data, an About page, and a cleaner identity graph. I wrote about that in Making My Website Machine-Readable.

Then I added one more piece for search engines that support it: IndexNow.

The idea is simple. Instead of waiting for a crawler to eventually discover that a page changed, your site can tell participating search engines directly when a URL is added, updated, or deleted.

That sounds like something I should just fire on every deploy.

I decided not to.

The interesting part was not sending the request. It was deciding where indexing logic should live, what URLs it should be allowed to submit, and when it should run.

What IndexNow actually gives me

IndexNow is a protocol supported by Bing and other participating search engines. The official setup guide boils the integration down to four things:

  1. generate a key
  2. host the key on your domain
  3. submit changed URLs
  4. verify submissions

It is important to separate submission from indexing.

IndexNow tells a search engine that something changed. It does not guarantee that the page will be crawled, indexed, or ranked.

For my portfolio, that is still useful.

I publish notes, change project pages, update resources, and occasionally remove or rename routes. I already maintain a canonical sitemap, so IndexNow became another way to notify search engines about those changes without inventing a second URL inventory.

I kept the verification file boring

The protocol needs a key file that proves the submitted URLs belong to the same host.

Because this is a Next.js app, the simplest implementation is a static file inside public/:

public/
└── <indexnow-key>.txt

The deployed URL becomes:

https://www.jansencadorna.com/<indexnow-key>.txt

and the response body contains only the key.

I initially thought about making the verification path dynamic, but there was no real advantage. The key is intentionally public for verification. A static file has fewer moving parts and makes the expected production URL obvious.

The submission helper has one job

I put the protocol logic in a small server-side helper instead of spreading it across routes or components.

The request payload looks roughly like this:

{
  host: "www.jansencadorna.com",
  key,
  keyLocation: `https://www.jansencadorna.com/${key}.txt`,
  urlList
}

and it is sent to:

https://api.indexnow.org/indexnow

The useful part is everything I validate before that request happens.

My helper rejects:

  • non-HTTPS URLs
  • localhost
  • external domains
  • URLs with unexpected ports
  • empty submissions
  • more than 10,000 URLs in one request

It also removes URL fragments and deduplicates the final list.

That means this:

/blog/post#section
/blog/post
https://www.jansencadorna.com/blog/post

becomes one canonical submission instead of three slightly different strings.

I wanted the utility to be strict because indexing code should not quietly accept whatever URL gets passed into it. If a future script accidentally tries to submit a preview deployment or another domain, I would rather fail immediately.

I reused the sitemap instead of duplicating routes

This was probably the implementation decision I cared about most.

The site already knows which URLs are canonical because the sitemap generator knows.

So I did not create another array like:

const indexNowUrls = [
  "/",
  "/about",
  "/blog",
  "/works",
  // ...and eventually forget to update this
]

Instead, both Next.js and the IndexNow script use the same sitemap source.

Conceptually:

canonical route inventory
        |
        |-- sitemap.xml
        |
        `-- IndexNow sitemap submission

That gives me one source of truth for indexable URLs.

When a new blog post or resource becomes part of the sitemap, the full IndexNow submission can discover it automatically.

This is a small architecture decision, but it prevents a common problem: two SEO systems slowly disagreeing about which pages exist.

I still wanted a targeted command

Submitting the whole sitemap is useful when I first configure the integration or make broad changes.

Most of the time, though, I know exactly what changed.

So I added a command for explicit URLs:

pnpm indexnow /blog/new-post /works

Relative paths are normalized against the production domain before submission.

I also added:

pnpm indexnow:sitemap

for submitting every canonical URL currently exposed by the sitemap.

The important part is that neither command is connected to normal page requests.

Visiting /blog does not ping IndexNow.

Rendering a Server Component does not ping IndexNow.

The indexing side effect stays explicit.

Why I did not run it inside next build

This was the part I almost automated too early.

It would be easy to attach IndexNow submission to the build process and say:

deploy -> build -> submit URLs

But a Vercel build running successfully is not the same thing as the production deployment being live and healthy.

There are also preview deployments.

There are failed releases.

And the repository does not currently maintain a perfect changed-URL map that knows about every addition, update, deletion, and rename.

So I kept the first version intentionally less clever:

production deploy succeeds

explicit IndexNow submission

That is one extra command, but it gives me much clearer control over when search engines are notified.

If I automate it later, I would rather make it a post-deployment job that runs only after production succeeds than something buried inside the build itself.

Missing configuration should not make the site fail

The helper reads the key from INDEXNOW_KEY.

If the variable is missing, the library itself returns a skipped result instead of making a network request.

That lets the core application remain independent from IndexNow.

The CLI scripts can still treat missing configuration as an error, because a command whose entire purpose is "submit URLs" should tell me when it did not submit anything.

That distinction is useful:

library behavior -> safe to skip
explicit CLI action -> fail visibly

I tested the boring failure cases

The happy path is one POST request.

The tests are mostly about making sure future changes cannot loosen the boundaries accidentally.

I covered things like:

  • relative URL normalization
  • canonical absolute URLs
  • external-domain rejection
  • localhost rejection
  • HTTPS enforcement
  • duplicate removal
  • the 10,000 URL limit
  • missing-key behavior
  • key location generation
  • the exact request payload
  • failed API responses

No real IndexNow request runs during the test suite.

That is the kind of SEO integration I prefer: small enough to understand, but strict enough that it does not become a mysterious deployment side effect.

IndexNow did not replace the rest of SEO

I do not think of IndexNow as a ranking trick.

It does not replace:

  • useful content
  • internal links
  • canonical URLs
  • sitemap.xml
  • structured data
  • search-engine webmaster tools
  • external authority

It just improves the notification path for engines that participate in the protocol.

That is also why I kept it separate from my work around Google Search. Google Search Console, structured data, and the site's content strategy still have their own roles.

IndexNow is simply another piece of the infrastructure.

What I would keep if I rebuilt it

If I had to add IndexNow to another Next.js site, I would keep the same rules:

  1. Use a static verification file. Fewer moving parts.
  2. Keep one canonical URL inventory. Reuse the sitemap source instead of maintaining two lists.
  3. Validate the host aggressively. Never allow preview, localhost, or external URLs through by accident.
  4. Do not submit on page views. Indexing notifications are publishing events, not request-time behavior.
  5. Separate build from post-deploy work. A successful build is not the same thing as a healthy production release.
  6. Treat submission as notification, not guaranteed indexing. The search engine still decides what happens next.

The code itself was not difficult.

The useful part was giving the integration clear boundaries.

That has become a pattern in how I build things: the API call is usually the easy part. Deciding when it should be allowed to happen is where the engineering starts.