Static, But Refresh Every N Seconds
Incremental Static Regeneration
ISR serves cached HTML to visitors and quietly regenerates it in the background after a TTL — static speed with periodic freshness.
What you'll learn
- Set a TTL with `next.revalidate` or `export const revalidate`
- Understand the stale-while-revalidate model
- Bust the cache on demand with `revalidatePath`
ISR sits between static and dynamic. The page is prerendered like SSG, but Next periodically rebuilds it in the background so users see fresh data without paying for SSR on every request.
Per-fetch Revalidation
// app/posts/page.tsx
export default async function Page() {
const posts = await fetch('https://api.example.com/posts', {
next: { revalidate: 60 },
}).then((r) => r.json())
return <Posts data={posts} />
} After 60 seconds, the next request still gets the stale HTML instantly, and Next triggers a rebuild in the background. The request after that receives the fresh copy.
Per-route Revalidation
// app/posts/page.tsx
export const revalidate = 60
export default async function Page() {
// every fetch on this route inherits the 60s TTL
} The route-level revalidate export applies to every cached fetch on the page that
doesn’t override it.
Stale-While-Revalidate
The first request after the TTL expires does not block — it serves the stale page and schedules a rebuild. That means the cache is “eventually fresh”, not “always fresh”.
On-Demand Invalidation
// app/actions.ts
'use server'
import { revalidatePath } from 'next/cache'
export async function publishPost() {
// ...write to DB...
revalidatePath('/posts')
} revalidatePath busts the cache immediately. The next visitor triggers a rebuild
instead of waiting for the timer.