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.
// next.config.ts
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
cacheComponents: true,
partialPrefetching: true,
}
export default nextConfigWith 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.
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/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/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/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/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
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.
Frequently Asked Questions
Does ISR still use the revalidate export in Next.js 16?
Is ISR supported on a static export?
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.
More in this topic
`generateMetadata` Explained with Real Examples
What generateMetadata does, when it runs, and how to use it for real routes: awaited params, deduplicated data fetching, extending parent metadata, and returning a 404 from metadata.
Canonical URLs in Next.js: `metadataBase`, `alternates.canonical`, and Dynamic Pages
How canonical URLs work in the Next.js App Router: setting metadataBase once, writing alternates.canonical per route, handling dynamic segments, and what happens when the base URL is missing.
Open Graph and Twitter Card Metadata in Next.js
How to write Open Graph and Twitter card metadata in the Next.js App Router: the openGraph and twitter fields, automatic card defaults, article tags, and image merge rules.