When to Use Next.js Incremental Static Regeneration (ISR) in the App Router
ISR lets you keep static pages fresh without full rebuilds. This post explains when ISR is the right choice, walks through a product‑page example with time‑based and on‑demand revalidation, and discusses the staleness‑vs‑cost trade‑off.
11 Feb 2026, 19:37 UTC

Why ISR Matters for Product Pages
Imagine a catalog of 10,000 products. Each product page is read thousands of times a day but only changes when a price update or inventory status changes. Rebuilding the entire site on every change is costly, yet serving stale data forever is unacceptable. Incremental Static Regeneration (ISR) solves this by keeping a static snapshot for quick delivery and regenerating it in the background after a configured interval or on demand.
What ISR Is, in Short
In Next.js 15, ISR is a page‑level stale‑while‑revalidate strategy:
- At build time, the page is prerendered to static HTML.
- When a user requests the page, the cached HTML is served immediately.
- In the background, Next.js rebuilds the page if the revalidation interval has elapsed or if an on‑demand trigger fires.
- The next request receives the freshly regenerated page.
All of this happens without a full site rebuild and with minimal runtime cost.
When to Pick ISR over Static or Dynamic
Choose ISR when:
- Content changes infrequently relative to read traffic.
- Personalization is not required (ISR pages are shared across users).
- You want near‑real‑time freshness without the overhead of server‑side rendering on every request.
Use fully static generation when content only changes at deploy time, e.g., marketing copy. Use fully dynamic rendering when each request must be freshly computed, such as personalized dashboards.
Example: A Product Detail Page with Time‑Based and On‑Demand Revalidation
Below is a minimal App Router implementation for a product page that:
- Prerenders known product IDs at build time.
- Serves unknown IDs with a fallback that caches the first response.
- Fetches price data with a 60‑second revalidation window.
- Tags the fetch so a CMS webhook can trigger on‑demand regeneration.
/* app/products/[id]/page.tsx */
import { revalidateTag } from "next/cache";
export const revalidate = 60; // seconds for page‑level ISR
export async function generateStaticParams() {
// Pre‑render a handful of popular products
return [{ id: "123" }, { id: "456" }];
}
export default async function ProductPage({ params }: { params: { id: string } }) {
const product = await fetchProduct(params.id);
return (
{product.name}
Price: ${product.price}
{product.description}
);
}
async function fetchProduct(id: string) {
const res = await fetch(`https://api.example.com/products/${id}`, {
next: { revalidate: 60, tags: ["product"] },
});
return res.json();
}
When the CMS updates a product price, it can call a server route that triggers on‑demand revalidation:
/* app/api/webhook/product-update/route.ts */
import { revalidateTag } from "next/cache";
export async function POST(request: Request) {
const { productId } = await request.json();
// Invalidate the tag so the next request rebuilds the page
await revalidateTag("product");
return new Response("OK", { status: 200 });
}
Because the fetch uses the same tag, the next user who visits the product page will see the updated price without waiting for a deploy.
Staleness vs. Cost Trade‑off
ISR introduces a bounded staleness window:
- Time‑based revalidation (e.g.,
revalidate = 60) means a user could see data up to 60 seconds old. - The request that triggers regeneration may still receive the stale copy; the new build happens asynchronously.
- On‑demand revalidation shrinks this window to near‑zero after the webhook fires, but requires a reliable trigger and extra code.
Cost considerations:
- Static pages are served from CDN, incurring negligible per‑request cost.
- ISR rebuilds happen on the server; each rebuild consumes compute time, but only for pages that have changed.
- In serverless environments, the rebuild may trigger a cold start, adding latency for the first post‑revalidation request.
| Strategy | Staleness | Per‑Request Cost | Implementation Complexity |
|---|---|---|---|
| Static | None (only at deploy) | Zero (CDN) | Low |
| ISR (time‑based) | Bounded by revalidate seconds | Low (one rebuild per interval) | Medium (add revalidate export) |
| ISR (on‑demand) | Near‑zero after trigger | Low (rebuild only on trigger) | High (webhook + tag handling) |
| Dynamic | Zero (always fresh) | High (compute per request) | High |
Deployment Nuances
ISR’s cache lives with the deployment. In a self‑hosted environment that stays up, the cache persists across requests, giving consistent performance. In serverless platforms where functions can spin down between requests, the first request after a cooldown may trigger a fresh build, causing a latency spike. To mitigate this, consider:
- Using a CDN that supports edge caching for ISR pages.
- Setting a generous
revalidateinterval to reduce rebuild frequency. - Monitoring build times and scaling resources if rebuild latency becomes a bottleneck.
Practical Checklist Before Deploying ISR
- Pin your Next.js version (e.g., 15.0.0) and read the official ISR documentation for that release.
- Run
next buildlocally and inspect the route summary to confirm which pages are ISR. - Deploy to a preview environment, trigger a data change in the backend, and observe the staleness window.
- If using on‑demand revalidation, test the webhook end‑point to ensure the tag invalidation propagates.
- Verify that cached pages do not contain user‑specific data; otherwise, fall back to dynamic rendering.
Takeaway
ISR gives you the sweet spot between the speed of static delivery and the freshness of dynamic rendering. Use it for content that is read much more often than it changes, and pair time‑based revalidation with on‑demand tags when you need tighter freshness. Remember that ISR pages are shared across users, so keep them stateless, and always validate the behavior in your chosen deployment environment.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.