Incremental Static Regeneration (ISR)
Static Site Generation gives you the speed of pre-rendered HTML, but a purely static page can go stale the moment your data changes. Incremental Static Regeneration (ISR) solves this by letting Next.js regenerate a statically rendered page in the background, after it has already been served to users, without you having to rebuild and redeploy the whole site.
With ISR, a page is generated at build time (or on first request) just like a normal static page. Then, after a time interval you define, or in response to an explicit signal from your code, Next.js quietly regenerates that page behind the scenes and swaps in the fresh version — all while continuing to serve fast, cached HTML to visitors in the meantime.
Enabling ISR with revalidate
The simplest way to opt a page into ISR is to add a revalidate export to a route segment, or pass a next.revalidate option to fetch. Either tells Next.js how many seconds a cached version of the page is allowed to live before it becomes eligible for regeneration.
// app/[locale]/blog/[slug]/page.tsx
// Revalidate this route at most every 60 seconds
export const revalidate = 60
async function getPost(slug: string) {
const res = await fetch(`https://api.example.com/posts/${slug}`)
return res.json()
}
export default async function PostPage({ params }: { params: { slug: string } }) {
const post = await getPost(params.slug)
return (
<article>
<h1>{post.title}</h1>
<p>{post.body}</p>
</article>
)
}The same behavior can be scoped to an individual fetch call instead of the whole route, which is useful when a page combines data that changes at different rates.
async function getPost(slug: string) {
const res = await fetch(`https://api.example.com/posts/${slug}`, {
next: { revalidate: 60 },
})
return res.json()
}Stale-while-revalidate behavior
ISR follows a stale-while-revalidate model, the same idea used by HTTP caching for decades:
A request comes in before the revalidate window has elapsed. Next.js serves the cached page immediately — no regeneration happens.
A request comes in after the revalidate window has elapsed. Next.js still serves the existing (now "stale") cached page immediately, so the visitor never waits.
In the background, Next.js triggers a regeneration of that page.
Once regeneration succeeds, Next.js invalidates the old cache and swaps in the newly generated page for subsequent requests.
If regeneration fails, the previously cached page keeps being served, and Next.js retries on a later request.
ISR vs. fully static vs. fully dynamic
Strategy | When HTML is generated | Freshness | Typical use case |
|---|---|---|---|
Static (no revalidate) | Build time only | Never updates without a redeploy | Legal pages, marketing pages |
ISR (revalidate: N) | Build time, then regenerated in the background | Eventually consistent, up to N seconds stale | Blog posts, product pages, dashboards |
Dynamic (force-dynamic / no-store) | On every request | Always fresh | User-specific or highly volatile data |
On-demand revalidation
Time-based revalidation is a blunt instrument — it regenerates on a schedule whether or not anything actually changed. When you know exactly when data changed (for example, right after an editor saves a blog post), you can trigger regeneration immediately and precisely with revalidatePath or revalidateTag, typically called from a Server Action or a Route Handler.
'use server'
import { revalidatePath } from 'next/cache'
export async function publishPost(slug: string) {
await savePostToDatabase(slug)
// Force Next.js to regenerate this exact path right now
revalidatePath(`/blog/${slug}`)
}Things to watch for
revalidate is a route-segment config — it applies to the whole page/layout, not just one fetch call.
If a page has multiple revalidate values across nested layouts and pages, Next.js uses the lowest (most frequent) one.
Setting revalidate = 0 or using cache: "no-store" opts a route out of static caching entirely, making it fully dynamic.
ISR works the same way whether you deploy to a Node.js server or to a platform with edge/serverless caching support — the underlying primitive is the same.