How Incremental Static Regeneration (ISR) Works in Next.js
ISR (Incremental Static Regeneration) is a strategy that combines the benefits of SSG and SSR. The page is generated statically but can update in the background at a set interval without a full application rebuild.
The Problem ISR Solves
Imagine Hack Frontend has a problem catalog. It updates a couple of times a day. SSG does not fit because after adding a new problem you need a full next build. SSR does not fit because the catalog is the same for all users and there is no point generating it on every request.
ISR solves this dilemma: the page is static but knows how to update itself.
How It Works
Initial build
The page is generated statically, just like SSG. The HTML is cached.
Request within revalidate window
The user gets the cached version of the page. No server rendering.
Request after revalidate expires
The user still gets the cached version (stale), but Next.js triggers regeneration in the background. The next user will get the updated page.
This approach is called stale-while-revalidate: serve the stale version while preparing the fresh one.
ISR in App Router
In the App Router ISR is configured via the revalidate parameter in fetch:
// app/problems/page.tsx
export default async function ProblemsPage() {
const res = await fetch('https://api.hackfrontend.com/problems', {
next: { revalidate: 300 }
})
const problems = await res.json()
return (
<div>
<h1>Problems on Hack Frontend</h1>
<ul>
{problems.map((p: { id: string; name: string }) => (
<li key={p.id}>{p.name}</li>
))}
</ul>
</div>
)
}
revalidate: 300 means: cache the page for 5 minutes, then update in the background.
Segment-Level Configuration
You can set revalidate for an entire route:
// app/problems/layout.tsx
export const revalidate = 300
export default function ProblemsLayout({
children
}: {
children: React.ReactNode
}) {
return <>{children}</>
}
On-Demand Revalidation
Instead of an interval you can update the page on an event. For example, after adding a new problem:
// app/api/revalidate/route.ts
import { revalidatePath, revalidateTag } from 'next/cache'
import { NextResponse } from 'next/server'
export async function POST(request: Request) {
const { secret, path } = await request.json()
if (secret !== process.env.REVALIDATION_SECRET) {
return NextResponse.json({ error: 'Invalid secret' }, { status: 401 })
}
revalidatePath(path)
return NextResponse.json({ revalidated: true })
}
Or using tags:
// Fetching data with a tag
const res = await fetch('https://api.hackfrontend.com/problems', {
next: { tags: ['problems'] }
})
// Invalidate by tag after data changes
revalidateTag('problems')
In practice:
On-demand revalidation is more convenient than the interval approach. You update the page exactly when the data changes, not after a fixed period.
ISR in Pages Router (Legacy)
In the Pages Router ISR is enabled by adding revalidate to getStaticProps:
// pages/problems.tsx
export async function getStaticProps() {
const res = await fetch('https://api.hackfrontend.com/problems')
const problems = await res.json()
return {
props: { problems },
revalidate: 300
}
}
When to Use ISR
Good for:
- Catalogs and lists (problems, products, courses)
- Content updated a few times a day
- Pages where a few minutes of staleness is acceptable
- Large sites where a full rebuild takes too long
Not suitable for:
- Personalized content (use SSR)
- Real-time data (use SSR or CSR with WebSocket)
- Content that never changes (SSG is enough)
Strategy Comparison
| SSG | ISR | SSR | |
|---|---|---|---|
| Generation | At build | At build + background update | Every request |
| Freshness | At build time | With delay (revalidate) | Always current |
| Speed | Instant | Instant | Depends on server |
| Rebuild | Full | Not needed | Not needed |
Useful Resources
- nextjs.org/docs — data revalidation
- On-Demand Revalidation — event-based invalidation