SyntaxStudy
Sign Up
Next.js Incremental Static Regeneration (ISR) Deep Dive
Next.js Beginner 1 min read

Incremental Static Regeneration (ISR) Deep Dive

Incremental Static Regeneration allows you to update statically generated pages after deployment without rebuilding the entire site. ISR uses a stale-while-revalidate strategy: when a request arrives for a cached page that has exceeded its revalidation period, Next.js serves the stale cached version immediately and triggers a background regeneration. The next request will receive the freshly regenerated page. In the App Router, you configure ISR by setting the revalidate export on a page or by passing next.revalidate to fetch() calls. Setting export const revalidate = 60 means the page will be regenerated at most once every 60 seconds. Setting revalidate = 0 is equivalent to force-dynamic. Setting revalidate = false (the default) means cache forever until explicitly invalidated. On-demand revalidation, introduced in Next.js 12.1, allows you to purge the ISR cache immediately when content changes rather than waiting for the time interval to expire. Your CMS or back-end calls a webhook Route Handler that calls revalidateTag() or revalidatePath(), immediately marking the relevant cached pages as stale. The next visitor triggers a fresh regeneration, so stale content is served for at most one request after a content change.
Example
// app/products/page.tsx — ISR with on-demand revalidation

// Cache this page and regenerate at most every 10 minutes
export const revalidate = 600;

async function getProducts() {
  const res = await fetch('https://api.example.com/products', {
    next: {
      revalidate: 600,
      tags: ['products-list'],
    },
  });
  return res.json();
}

export default async function ProductsPage() {
  const products = await getProducts();
  return (
    <ul>
      {products.map((p: { id: string; name: string; price: number }) => (
        <li key={p.id}>
          {p.name} — ${p.price}
        </li>
      ))}
    </ul>
  );
}

// app/api/webhooks/cms/route.ts — on-demand revalidation webhook
import { revalidateTag } from 'next/cache';
import { NextRequest, NextResponse } from 'next/server';

export async function POST(request: NextRequest) {
  const secret = request.headers.get('x-webhook-secret');

  if (secret !== process.env.WEBHOOK_SECRET) {
    return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
  }

  const payload = await request.json();

  // CMS sends the model name; invalidate matching tag
  if (payload.model === 'product') {
    revalidateTag('products-list');
  }

  return NextResponse.json({ revalidated: true, at: new Date().toISOString() });
}