Helm7

Incremental Static Regeneration When You Self-Host

The Helm7 team · Published 18 September 2026 · Checked 30 September 2026 · 8 min read

incremental static regeneration: a glass hourglass on a wooden table
Photo by Towfiqu barbhuiya on Pexels

Incremental Static Regeneration is meant to be easy: one line of config and a page updates itself. Then you self-host, add a second server, and two visitors see two versions of the same page. This post follows a single page through its requests, shows the headers that prove what Next.js is doing, and marks the exact point where one server stops being enough.

In short

  • Incremental Static Regeneration serves a saved page instantly and rebuilds it in the background once its revalidate time has passed.
  • The x-nextjs-cache header tells you what happened: HIT, STALE, MISS or REVALIDATED.
  • Use a long revalidate time as a safety net, and revalidatePath or revalidateTag for changes you know about.
  • One instance needs no setup. With two or more, the cache and tag invalidations are per instance until you share them with a custom cache handler.
  • ISR does not work in a static export, and a fetch with revalidate 0 turns the whole route dynamic.

What is Incremental Static Regeneration?

Incremental Static Regeneration, or ISR, lets a page behave like a static file and still get fresh content. Next.js renders the page once and keeps the result. Visitors get that saved copy straight away, with no render in the request path. After a set time, Next.js rebuilds the page in the background and swaps in the new copy.

The Next.js guide lists four things it buys you: updating static content without rebuilding the whole site, less server load, correct cache-control headers added for you, and no long builds when you have thousands of pages. That last one matters more than it sounds. Picture a shop with 20,000 product pages. It does not want to render all 20,000 on every deploy.

It is stale-while-revalidate applied to a whole page. Nobody waits for the rebuild. One visitor gets the old page, and the next visitor gets the new one.

Follow one page through five requests

Take a blog page with export const revalidate = 60. Here is what happens to it, from the Next.js docs, with the x-nextjs-cache response header you can watch in your browser tools. The docs define four values: HIT, STALE, MISS and REVALIDATED.

One ISR page with revalidate set to 60 seconds: what each request sees
WhenWhat happensVisitor getsx-nextjs-cache
Build time, 0sPage prerendered and savedNothing yetNone
10sInside the 60 second windowSaved copy, instantlyHIT
75sWindow has passed. Next.js starts a background renderThe old copy, still instantlySTALE
80sBackground render finished and cachedThe new copyHIT
A path never built, first ever requestRendered on demand, then savedA fresh render, slower onceMISS

Notice the 75 second row. The visitor who triggers the rebuild does not wait for it. They are served stale. The price of ISR is that somebody always sees the old page once.

If the background render throws an error, the docs say the last good copy keeps being served and Next.js retries on the next request. A broken CMS call does not take your page down.

How does ISR differ from SSG and SSR on the wire?

The three strategies send different Cache-Control headers, which is the easiest way to tell which one you are really getting. These are the values from the self-hosting guide.

Cache-Control headers Next.js sets, from the self-hosting guide
Kind of responseCache-Control headerMeaning
Immutable assets, hashed file namespublic, max-age=31536000, immutableCache for one year. It cannot be overridden
ISR pages-maxage=<revalidate>, stale-while-revalidateFresh for your revalidate seconds, then serve stale while rebuilding
Dynamic (SSR) pageprivate, no-cache, no-store, max-age=0, must-revalidateNever cached, so user data cannot leak

Plain static generation is the first row in spirit: built once and never rebuilt until you deploy. SSR is the third row: rendered on every visit. ISR sits between them. Pick it for pages that are right most of the time and fine to be a few minutes behind when they change, such as blog posts, docs and pricing.

Should you revalidate on a timer or on demand?

“We recommend setting a high revalidation time. For instance, 1 hour instead of 1 second.”
Next.js documentation, How to implement Incremental Static Regeneration (ISR)

A short timer is a blunt tool. It makes more background renders and still leaves a window where the page is wrong. The better pattern is a long timer as a safety net, plus on-demand revalidation when something actually changes.

  • revalidatePath with a route such as /posts invalidates that page. The next request regenerates it. Use it after a form or a publish button.
  • revalidateTag with a tag and the second argument max invalidates everything you tagged, for example every fetch tagged posts. Use it when one change touches many pages.
  • The docs prefer whole paths for most cases and keep tags for when you need finer control.

One catch: in the App Router, revalidatePath marks the entry as invalid, but the regeneration happens on the next request, not straight away. So the first visitor after a publish still pays for the render.

Does ISR need Vercel?

No. Incremental Static Regeneration is a Next.js feature, and the docs list it as supported on a Node.js server and in a Docker container. It is not supported in a static export, and it is not supported on the Edge runtime. On one instance with a persistent disk it needs no configuration. The cache lives on that disk, and next start handles the rest.

Two self-hosting details are worth knowing. By default the cache is held in memory, 50MB, and on disk. And if you run in a container with no volume, the disk cache is empty after every deploy. That is harmless. Pages rebuild on their next request. If you want to see it happen, the Dockerfile in our Docker guide is a good place to test it.

Where ISR is supported, from the Next.js ISR guide
Deployment optionISR supported
Node.js server (next start)Yes
Docker containerYes
Static exportNo
Adapters (platform-specific)Depends on the platform

What breaks Incremental Static Regeneration on more than one server?

This is the part most ISR posts skip. The cache is per instance. The docs say it plainly: with several instances, on-demand revalidation only invalidates the instance that receives the call.

Here is a worked example with assumed numbers. You run two containers, A and B, behind a load balancer. Your pages use revalidate = 3600. An editor hits Publish, which calls revalidatePath. The request lands on A. A drops its copy and rebuilds on the next visit. B knows nothing about it. B keeps serving the old page for up to an hour, until its own timer expires. Half your visitors see the update, and half do not, and it flips as the balancer rotates them.

  1. 1Share the cachePoint cacheHandler in next.config at shared storage such as Redis, and set cacheMaxMemorySize to 0 so the in-memory copy stops overriding it.
  2. 2Share the tagsImplement updateTags and refreshTags in the handler. The docs say refreshTags is called before every request, so an invalidation on one instance reaches the others.
  3. 3Catch errors in refreshTagsIf it throws, the exception becomes a failed request. Catch it and carry on with the last known state.
  4. 4Use the same build everywhereBoot every container from one build, and set one shared NEXT_SERVER_ACTIONS_ENCRYPTION_KEY, or Server Actions fail across instances.

That is real work, and it is the reason a second instance is a decision rather than a slider. The guide to self-hosting Next.js walks through the full list, including the deployment id for rolling deploys.

Why is my Incremental Static Regeneration page stuck on old content?

Work down this list before you blame the cache. Every item comes from the Next.js caveats.

  • A fetch on the route has revalidate set to 0 or no-store. The whole route is then rendered dynamically, so ISR never applies.
  • Several fetches disagree. The lowest revalidate on the route wins for ISR.
  • You revalidated a rewritten path. Proxy does not run for on-demand ISR requests, so revalidate the real path, /post/1 rather than /post-1.
  • You are testing with next dev. Run next build then next start to see production behaviour.
  • You are on a static export, which does not support ISR at all.

To watch hits and misses in the server log, add NEXT_PRIVATE_DEBUG_CACHE=1 to your .env file, as the ISR guide suggests.

If you are still choosing where to run all this, how to host a Next.js app compares the options.

How Helm7 fits

Helm7 runs each environment as one instance, which is the case where Incremental Static Regeneration needs no configuration. It builds your repository in a container, starts the new version, waits for it to answer, moves traffic across and gives the old one 30 seconds to finish. Running several instances that share a cache is planned, and not offered yet.

Common questions

  • Do I need Vercel for incremental static regeneration?

    No. The Next.js docs list ISR as supported on a Node.js server and in a Docker container. It works with next start on any host, and needs extra setup only when several instances must share one cache.

  • How is ISR different from a CDN cache?

    A CDN caches a URL until it expires or you purge it. ISR is built into the framework, tracks its own revalidation time per page, and can be triggered from your own code with revalidatePath or revalidateTag, with no CDN purge call.

  • What happens to the ISR cache when I redeploy?

    In a container with no persistent volume the cache is empty after each deploy, and pages rebuild on their next request. The first visitor to each page waits slightly longer. Mount a volume or use a shared cache handler if that matters to you.

  • Can I use ISR for per-user pages?

    Not directly. ISR keeps one copy of a page per URL, shared by every visitor. Per-user content has to be fetched in the browser or rendered dynamically, because the saved HTML cannot differ by visitor.

  • How do I check whether a page came from the ISR cache?

    Look at the x-nextjs-cache response header. HIT means served from cache, STALE means served from cache while it rebuilds, MISS means it was rendered fresh, and REVALIDATED means it was regenerated by on-demand revalidation.

Sources

  1. Next.js docs: How to implement Incremental Static Regeneration (ISR)
  2. Next.js docs: How to self-host your Next.js application
  3. Next.js docs: How revalidation works in Next.js

Run your entire app stack, end to end, from $12 a month.

App, Postgres, auth, storage, jobs and domains on one platform, planned and priced before anything changes.

More from the blog