Skip to content

Incremental Static Regeneration (ISR)

Incremental Static Regeneration (ISR) lets you get the performance of static rendering while still updating content periodically, without a full rebuild and redeploy. This lesson covers time-based and on-demand revalidation.

Static Pages That Update Themselves

ISR works by statically rendering a route (or a specific fetch call within it) and attaching an expiration window. The first request after that window expires still receives the existing cached HTML instantly, but Next.js regenerates the page in the background and swaps in the fresh version for subsequent requests — visitors never wait on a slow regeneration.

This makes ISR ideal for content that changes occasionally but doesn't need to be instantaneous — product listings, blog indexes, or marketing pages backed by a CMS that gets updated a few times a day.

// app/products/page.tsx
export default async function ProductsPage() {
  const products = await fetch("https://api.example.com/products", {
    next: { revalidate: 300 }, // regenerate at most every 5 minutes
  }).then((r) => r.json());

  return <ul>{products.map((p: any) => <li key={p.id}>{p.name}</li>)}</ul>;
}

For the first five minutes after generation, every visitor gets the same cached HTML instantly; after that, the next request triggers a background regeneration.

Two Ways to Enable ISR

// Per-fetch revalidation
fetch(url, { next: { revalidate: 60 } });

// Whole-route revalidation (route segment config)
export const revalidate = 60;
  • next: { revalidate: N } on a specific fetch() call sets that call's staleness window in seconds.
  • export const revalidate = N at the top of a page sets a default revalidation window for the whole route.
  • A revalidate value of 0 behaves like no-store (always dynamic); false means "cache forever, no automatic revalidation".
  • Time-based revalidation happens lazily — it triggers on the next request after expiration, not on a background timer.

ISR Cheat Sheet

Configuration options and what they mean.

Setting Effect
revalidate: 60 Regenerate at most once every 60 seconds
revalidate: false Cache indefinitely, no automatic revalidation
revalidate: 0 Effectively disables ISR for that fetch (always dynamic)
revalidateTag("tag") Invalidate cached data on demand, from a Server Action or Route Handler
revalidatePath("/products") Invalidate a specific route's cache on demand

On-Demand Revalidation

Time-based revalidation is convenient but imprecise — content might update up to N seconds late. On-demand revalidation solves this by letting your own code (typically a Server Action or webhook-driven Route Handler) explicitly invalidate cached content the moment it actually changes.

"use server";
import { revalidatePath } from "next/cache";

export async function updateProduct(id: string, data: FormData) {
  await saveProductToDatabase(id, data);
  revalidatePath("/products");
}

The /products page regenerates immediately after this action runs, instead of waiting for its time-based window to expire.

The Stale-While-Revalidate Model

ISR follows a "stale-while-revalidate" pattern: visitors are never blocked waiting for regeneration. The currently cached (possibly slightly stale) version is served instantly while a fresh version is generated in the background for future requests.

  • No visitor ever experiences the regeneration delay directly.
  • At most one "stale" version is ever served after the revalidation window passes.
  • Regeneration failures fall back to serving the last successfully generated version.

Common Mistakes

  • Expecting content to update the instant the revalidation window expires, rather than on the next request after that.
  • Setting a very short revalidate value for expensive-to-generate pages, causing frequent, costly regenerations.
  • Forgetting revalidateTag()/revalidatePath() exist for cases where waiting on a timer isn't precise enough.
  • Confusing revalidate: false (cache forever) with revalidate: 0 (effectively no caching).

Key Takeaways

  • ISR combines static performance with periodic content freshness.
  • next: { revalidate } and export const revalidate both configure time-based revalidation.
  • Visitors always get an instant response — regeneration happens in the background, never blocking a request.
  • revalidateTag() and revalidatePath() enable precise, on-demand revalidation.
  • ISR is ideal for content that changes occasionally but doesn't require instant, per-request freshness.

Pro Tip

Pair a generous time-based revalidate window (as a safety net) with on-demand revalidatePath()/revalidateTag() calls triggered right after content actually changes — this gives you both guaranteed eventual freshness and near-instant updates when you know exactly when data changed.