Skip to content
techpotions
Next.js · sitemap · caching · isr · payloadJuly 29, 20264 min read

Next.js Sitemap Not Updating? Here's the Real Fix

If your Next.js sitemap is not updating after publishing new content, you're hitting a specific cache-coherence bug that isn't obvious from the docs.

Cover illustration for “Next.js Sitemap Not Updating? Here's the Real Fix”

Next.js Sitemap Not Updating? Here's the Real Fix

If your Next.js sitemap is not updating after you publish new content, you're dealing with a cache-coherence bug that almost nobody writes up. It has an exact symptom, a reproducible root cause, and a one-line fix. This is the guide you'll wish you had the moment you notice /sitemap.xml serving fewer entries than your real site.

The symptom: your sitemap lags behind your published content

The mismatch is impossible to miss once you look. On our own site, /lab lists 11 published posts, yet /sitemap.xml shows only 7. Same database, same deploy, two different answers. If that gap sounds familiar, you're in the right place.

You might have checked your afterChange hook, verified that revalidateTag('posts') fires, and even confirmed that the tagged data refreshes — only to find the sitemap still frozen. That's because the problem lives between two cache layers, not inside the data fetch.

Why revalidateTag doesn't fix a stale Next.js sitemap

The answer lies in what sitemap.ts actually is. According to the Next.js Metadata Files: sitemap.xml documentation, it's a special Route Handler. And like any Route Handler, Next.js caches its rendered output by default.

Here's what happened in our own repository (this bug is documented in a comment at the top of app/(frontend)/sitemap.ts because it cost real indexation time):

  1. We read content from Payload using unstable_cache, tagged with the collection slug posts.
  2. An afterChange hook called revalidateTag('posts') whenever a post was published.
  3. That call did work — it invalidated the inner unstable_cache data entry.
  4. But the route's statically-rendered outer XML output was never re-run. The frozen route output kept serving the old XML built from the old data, long after the inner cache was refreshed.

Two cache layers. Tag-based revalidation busted the inner one, but the outer route handler cache was never told to re-execute. That's the missing piece.

The one-line fix: route-level ISR on the sitemap

Tell the sitemap route to re-render on a bounded interval:

TypeScript
// app/(frontend)/sitemap.ts

export const revalidate = 3600; // seconds — re-render at most once per hour

That's it. With this line, the whole route re-executes after 3600 seconds and always picks up newly published documents. The sitemap is crawler-only traffic, so an hour of staleness is irrelevant. Even better, the bounded interval deduplicates the Payload read to at most once per hour, so you're not hammering the database on every bot request.

Once route-level ISR is in place, the inner unstable_cache becomes redundant. The route itself is now invalidating on a schedule, so you can drop the wrapper and simplify your data fetching.

Drop the inner cache after adding revalidate

Before, your sitemap might have looked like this:

TypeScript
import { unstable_cache } from 'next/cache';
import { getPayloadPosts } from '@/lib/payload';

export async function GET() {
  const posts = await unstable_cache(
    getPayloadPosts,
    ['sitemap-posts'],
    { tags: ['posts'] }
  )();

  // generate XML…
}

After setting export const revalidate = 3600, you can remove the unstable_cache wrapper entirely:

TypeScript
export const revalidate = 3600;

export async function GET() {
  const posts = await getPayloadPosts();
  // generate XML…
}

The route-level ISR already ensures the handler re-runs periodically, and on-demand revalidation can still be triggered with revalidatePath('/sitemap.xml') from your publish hook if you need an instant refresh. No more stale XML.

The general lesson: tag-based revalidation invalidates data, not rendered output

This bug taught us a rule that applies to every part of Next.js where you stack a data cache inside a route cache:

revalidateTag clears your data, not the route that renders it. If the route itself is cached (static generation, full route cache, etc.), invalidating inner data leaves the outer rendered output untouched.

Whenever you have both layers, you must either:

  • set route-level revalidate to give the outer layer a time bound, or
  • call revalidatePath on the route itself to manually bust the rendered output.

This is the piece that the revalidateTag documentation doesn't spell out, and it's the difference between a sitemap that stays fresh and one that silently sabotages your indexation.

After fixing your sitemap, tell search engines about the new content immediately — our IndexNow integration for Next.js pushes updates the moment you publish.

Struggling with caching or Next.js performance in general? Our team builds products where this stuff is ironed out before it bites you — start a project and we'll make sure your site's infrastructure stays boringly reliable.

FAQ

Why is my Next.js sitemap not updating even though revalidateTag works?

revalidateTag only clears data caches tagged by unstable_cache. Your sitemap route's rendered output is cached separately as a static route handler. To update the sitemap, you need to either revalidate the path with revalidatePath('/sitemap.xml') or set route-level ISR with export const revalidate.

Should I use unstable_cache in my sitemap route after adding revalidate?

No. After setting revalidate on the route, Next.js will re-render the whole handler periodically, making the inner data cache redundant. Remove unstable_cache to simplify.

What's the recommended revalidate interval for a sitemap?

An hour (3600 seconds) is safe. Sitemaps are consumed by crawlers sporadically, so a small delay won't hurt indexation, while it protects your database from unnecessary reads.

Written by
techpotions
All entries
Block AI Crawlers: The 15 Bots That Matter

Got a build in mind? Tell us about it.