Next.js Incremental Static Regeneration (ISR)
Incremental Static Regeneration gives you the speed of a static site with the freshness of server-side rendering. Pages are pre-built like SSG, but they automatically refresh in the background on a schedule you control. You get fast delivery and up-to-date content — without rebuilding the entire site.
The Hotel Room Analogy
Think of a hotel that cleans rooms on a schedule. The room is always ready (pre-built static page) when a guest arrives. Every night, housekeeping comes through and refreshes each room (background regeneration). Guests never wait — they always get a clean, ready room. And the room gets updated regularly without the hotel shutting down.
Pure SSG:
Build → Generate all pages → Serve forever (stale until rebuild)
Pure SSR:
Each request → Generate page fresh → Serve (always current, slower)
ISR:
Build → Generate pages → Serve (fast)
Background → Regenerate pages after revalidate time → Serve updated versionSetting Up ISR With revalidate
Add next: { revalidate: seconds } to your fetch call to enable ISR. The number is how many seconds Next.js waits before regenerating the page after the next visit.
// app/blog/page.js
async function getPosts() {
const res = await fetch("https://api.example.com/posts", {
next: { revalidate: 3600 }, // Regenerate at most once per hour
});
return res.json();
}
export default async function BlogPage() {
const posts = await getPosts();
return (
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
}You can also set revalidate as a route segment export instead of inside fetch:
// app/blog/page.js
export const revalidate = 3600; // 1 hour — applies to the entire route
async function getPosts() {
const res = await fetch("https://api.example.com/posts");
return res.json();
}
export default async function BlogPage() {
const posts = await getPosts();
return ( /* ... */ );
}How ISR Works Step by Step
Step 1: Build time
Next.js builds /blog and saves the HTML file.
Step 2: First visitor (within revalidate window)
Visitor arrives → gets cached HTML instantly → fast ✓
No regeneration triggered yet.
Step 3: Visitor arrives after revalidate time expires
Visitor arrives → gets OLD cached HTML (still fast) ✓
In background → Next.js regenerates the page with fresh data.
Step 4: Next visitor after regeneration finishes
Gets the NEW HTML ✓
Key insight: The first visitor after expiry gets the stale page.
The NEXT visitor gets the fresh page.
Nobody waits.Diagram: ISR Timeline
revalidate = 60 seconds
Time 0:00 → Page built, cached as v1
Time 0:05 → Visitor A → gets v1 (fast)
Time 0:30 → Visitor B → gets v1 (fast)
Time 0:55 → Visitor C → gets v1 (fast)
[60 seconds expire]
Time 1:05 → Visitor D → gets v1 (still fast!)
→ Triggers background regen with fresh data
→ Next.js fetches new data, builds v2
Time 1:08 → Background regen finishes, v2 saved
Time 1:15 → Visitor E → gets v2 (fresh data!) ✓ISR With Dynamic Routes
Dynamic pages like blog posts also support ISR. Combine generateStaticParams with revalidate to pre-build known pages and keep them fresh.
// app/blog/[slug]/page.js
export const revalidate = 1800; // Regenerate after 30 minutes
export async function generateStaticParams() {
const posts = await fetch("https://api.example.com/posts").then((r) => r.json());
return posts.map((post) => ({ slug: post.slug }));
}
async function getPost(slug) {
const res = await fetch(`https://api.example.com/posts/${slug}`, {
next: { revalidate: 1800 },
});
return res.json();
}
export default async function BlogPost({ params }) {
const post = await getPost(params.slug);
return (
<article>
<h1>{post.title}</h1>
<div>{post.content}</div>
</article>
);
}On-Demand Revalidation
Sometimes you do not want to wait for a timer. When a new blog post publishes, you want the blog list page updated immediately. On-demand revalidation lets you trigger a page refresh instantly from an API route.
Method 1: Revalidate a Path
// app/api/revalidate/route.js
import { revalidatePath } from "next/cache";
import { NextResponse } from "next/server";
export async function POST(request) {
const { path, secret } = await request.json();
// Protect with a secret token
if (secret !== process.env.REVALIDATE_SECRET) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
revalidatePath(path); // e.g., "/blog" or "/blog/my-post"
return NextResponse.json({ revalidated: true, path });
}Your CMS calls this API endpoint when content changes. Next.js immediately marks that path as stale and regenerates it on the next request.
Method 2: Revalidate a Cache Tag
// Tag the fetch with a label
async function getPost(slug) {
const res = await fetch(`https://api.example.com/posts/${slug}`, {
next: {
revalidate: 3600,
tags: ["posts", `post-${slug}`], // Give this data a tag
},
});
return res.json();
}
// Later, invalidate all pages that use tagged data
// app/api/revalidate/route.js
import { revalidateTag } from "next/cache";
export async function POST(request) {
const { tag } = await request.json();
revalidateTag(tag); // e.g., "posts" invalidates all pages tagged "posts"
return Response.json({ revalidated: true });
}Diagram: On-Demand Revalidation Flow
Content editor publishes new post in CMS
│
▼
CMS triggers webhook → POST /api/revalidate { path: "/blog" }
│
▼
Next.js marks /blog cache as stale
│
▼
Next visitor arrives at /blog
│
▼
Next.js regenerates /blog with fresh data (visible to visitor)
│
▼
All future visitors get updated page ✓Choosing a revalidate Interval
Content Type Suggested revalidate
────────────────────── ────────────────────
News articles 60–300 seconds
Blog posts 1800–3600 seconds (30–60 min)
Product pages 3600 seconds (1 hour)
Documentation 86400 seconds (1 day) or on-demand
Marketing pages 86400 seconds or on-demand
User-generated content Use on-demand revalidationfalse and 0 Values
export const revalidate = false;
// Cache forever — never auto-regenerate (same as pure SSG)
export const revalidate = 0;
// Never cache — always regenerate (same as SSR / force-dynamic)ISR With Multiple Fetches
When a page has multiple fetch calls with different revalidate values, Next.js uses the lowest value as the effective revalidation time for the entire page.
// This page will revalidate every 60 seconds
// because 60 is lower than 3600
const posts = await fetch("/api/posts", { next: { revalidate: 3600 } });
const trending = await fetch("/api/trending", { next: { revalidate: 60 } });
// ↑ lowest winsFallback Behavior for New Pages
When dynamicParams = true (the default), a new page slug that was not in generateStaticParams gets server-rendered on the first request and then cached as a static ISR page for future visitors.
Timeline for a new blog post published after build:
Request 1 (first visitor):
→ Page not in cache
→ Server renders page fresh
→ Page saved to ISR cache
→ Visitor receives page
Request 2+ (all future visitors):
→ Page found in cache
→ Served instantly (static speed)
→ Regenerates in background after revalidate timeISR vs Other Strategies Summary
Strategy Speed Freshness Cost
──────── ───── ───────── ────
SSG Fastest Stale (rebuild) Low
ISR Fast Auto-refresh Low
SSR Moderate Always fresh Higher
CSR Slow init Real-time HigherCommon ISR Mistakes
Mistake 1: Expecting immediate updates
// After setting revalidate: 3600
// You publish a new post
// Visit /blog — it still shows old posts
// Why: The revalidate timer hasn't expired yet
// Fix: Use on-demand revalidation with revalidatePath() or revalidateTag()Mistake 2: Mixing revalidate with cache: "no-store"
// ❌ This cancels out ISR — the fetch opts out of caching entirely
const res = await fetch("/api/data", {
cache: "no-store",
next: { revalidate: 60 }, // Ignored because cache: "no-store" wins
});Mistake 3: Using ISR for user-specific pages
// ❌ Wrong: ISR caches the same HTML for all users
// If page shows "Hello, Alice", ISR saves that HTML
// Bob visits → gets "Hello, Alice" from cache!
// ✓ Correct: Use SSR for user-specific pages
export const dynamic = "force-dynamic";Summary
ISR combines the speed of static HTML with the freshness of periodic regeneration. Set a revalidate time on your fetch calls or as a route segment export. Use on-demand revalidation with revalidatePath or revalidateTag to refresh pages instantly when content changes. ISR is the sweet spot for most content-heavy sites — blogs, e-commerce, documentation, and marketing pages that update regularly but not every second.
