Incremental Static Regeneration (ISR) in Next.js

How to update static pages without rebuilding the site, using both the Cache Components model and the older revalidate-based ISR in Next.js 16.

8 min read

Incremental Static Regeneration, or ISR, updates static pages without rebuilding the whole site. A page is prerendered once, then regenerated in the background as traffic arrives, so visitors keep getting fast static HTML while content stays reasonably fresh.

Next.js 16 has two ISR stories. This article covers the Cache Components model first, since that is the default direction, then the older revalidate-based model for comparison.

ISR under Cache Components

Two flags shape this model. Cache Components produces the App Shell, and Partial Prefetching upgrades that shell to a full route once the params are known, so a prefetched link lands on the upgraded page instead of the shell.

typescripttypescript
// next.config.ts
import type { NextConfig } from 'next'
 
const nextConfig: NextConfig = {
  cacheComponents: true,
  partialPrefetching: true,
}
 
export default nextConfig

With both enabled, ISR starts from the App Shell. generateStaticParams prerenders the URLs you list at build time, and any other URL is served the shell instantly, then upgraded in the background with its now-known params.

Serving the shell for unlisted params requires Next.js 16.3 or later. Earlier versions wait for a full server render before sending the response.

ISR with Cache Components

The first visitor sees the shell and streaming fallbacks. The second visitor to the same URL gets the fully upgraded page from the cache, because the upgrade finished in the background.

Preparing the route

List the param values worth prerendering. Popular pages ship fully static, while the long tail falls back to the shell.

App.tsxApp.tsx
// app/blog/[slug]/page.tsx
export async function generateStaticParams() {
  const res = await fetch('https://api.example.com/posts')
  const posts = await res.json()
  return posts.slice(0, 10).map((p: { slug: string }) => ({ slug: p.slug }))
}

The same file renders the page and keeps the params read inside Suspense so unlisted slugs still get a shell.

App.tsxApp.tsx
// app/blog/[slug]/page.tsx
import { Suspense } from 'react'
 
async function Post({ params }: { params: Promise<{ slug: string }> }) {
  const { slug } = await params
  const post = await fetch(`https://api.example.com/posts/${slug}`).then((r) => r.json())
  return <article>{post.title}</article>
}
 
export default function PostPage({ params }: { params: Promise<{ slug: string }> }) {
  return (
    <Suspense fallback={<p>Loading post...</p>}>
      <Post params={params} />
    </Suspense>
  )
}

The two blocks live in the same file. generateStaticParams defines the concrete pages, and the Suspense boundary is what lets every other slug resolve through the shell. For the shell itself, see App Shells in Next.js.

Time-based revalidation with cacheLife

Inside the Cache Components model, time-based regeneration is set per cached scope with cacheLife. Its revalidate property says how often the server regenerates content in the background, which mirrors the old ISR timer.

App.tsxApp.tsx
// app/lib/posts.ts
import { cacheLife } from 'next/cache'
 
export async function getPosts() {
  'use cache'
  cacheLife('hours')
  const res = await fetch('https://api.example.com/posts')
  return res.json()
}

After an hour, the next request still gets the cached result immediately while a fresh copy regenerates in the background. The presets seconds, minutes, hours, days, weeks, and max cover the common cadences, and hours here means a one hour revalidate with a one day expiry.

The older revalidate model

When Cache Components is disabled, a route segment config drives ISR. The revalidate export is in seconds, and the next visitor after the window triggers a background rebuild.

App.tsxApp.tsx
// app/blog/page.tsx
export const revalidate = 3600
 
export default async function Page() {
  const res = await fetch('https://api.vercel.app/blog')
  const posts = await res.json()
  return (
    <ul>
      {posts.map((post: { id: string; title: string }) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  )
}

This is the pre-Cache-Components model. It regenerates the whole route on a timer, and it still works in Next.js 16 when you are not using Cache Components.

On-demand revalidation

Both models support invalidating on demand after a mutation, which is what makes an edit appear straight away instead of waiting out the timer. revalidatePath refreshes a route, while revalidateTag and updateTag invalidate data tagged with cacheTag.

One detail catches people out: these functions mark the entry as invalid, they do not rebuild it on the spot. Regeneration happens when the next request arrives, so the first visitor after a mutation is the one who triggers the rebuild. For path-based invalidation in detail, see revalidatePath explained.

Caveats

ISR requires the Node.js runtime, so it does not work with a static export. Background regeneration runs on the instance that receives the triggering request, which costs extra compute on per-request billing platforms. The x-nextjs-cache response header reports the state: HIT, STALE, MISS, or REVALIDATED, which is the quickest way to confirm regeneration is happening in production.

Rune AI

Rune AI

Key Insights

  • ISR updates prerendered pages without redeploying the site.
  • Cache Components upgrade App Shells in the background after a first visit.
  • cacheLife revalidate mirrors the older ISR timer per function.
  • The export const revalidate model is pre-Cache-Components.
  • On-demand invalidation uses revalidatePath, revalidateTag, and updateTag.
RunePowered by Rune AI

Frequently Asked Questions

Does ISR still use the revalidate export in Next.js 16?

It can, but that belongs to the pre-Cache-Components model. With cacheComponents enabled, revalidation is driven by cacheLife and generateStaticParams.

Is ISR supported on a static export?

No. ISR needs the Node.js server runtime, so output: 'export' cannot use it.

Conclusion

ISR updates static pages without a full rebuild. Under Cache Components it serves an App Shell instantly and upgrades the page in the background, while the older model revalidates a whole route on a timer.