One of the biggest frustrations I’ve encountered with static site generation is the ‘stale content’ problem. You update a product price or a blog typo in your CMS, but the live site doesn’t reflect the change for another hour because of your revalidate timer. If you’re wondering how to implement on-demand ISR in Next.js, you’re looking for a way to break that timer and force an update the second your data changes.

Incremental Static Regeneration (ISR) is a powerhouse feature, but time-based revalidation is often too blunt a tool. On-demand ISR allows you to manually trigger a revalidation for a specific path. In my experience, this is the ‘gold standard’ for JAMstack architecture best practices because it combines the speed of static delivery with the flexibility of dynamic updates.

Prerequisites

Before we dive into the code, make sure you have the following set up:

Step-by-Step Implementation

VS Code editor showing the Next.js API route for revalidation with TypeScript
VS Code editor showing the Next.js API route for revalidation with TypeScript

Step 1: Create the Revalidation API Route

The core of on-demand ISR is a secure API endpoint that tells Next.js to purge the cache for a specific URL. In the App Router, we use the revalidatePath or revalidateTag functions. I prefer revalidatePath for simple page updates.

// app/api/revalidate/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { revalidatePath } from 'next/cache';

export async function POST(request: NextRequest) {
  const secret = request.nextUrl.searchParams.get('secret');
  const path = request.nextUrl.searchParams.get('path');

  // 1. Verify the secret to prevent unauthorized cache purging
  if (secret !== process.env.MY_REVALIDATE_TOKEN) {
    return NextResponse.json({ message: 'Invalid token' }, { status: 401 });
  }

  if (!path) {
    return NextResponse.json({ message: 'Path is required' }, { status: 400 });
  }

  try {
    // 2. Trigger the revalidation
    revalidatePath(path);
    return NextResponse.json({ revalidated: true, now: Date.now() });
  } catch (err) {
    return NextResponse.json({ message: 'Error revalidating' }, { status: 500 });
  }
}

As shown in the API route above, the most critical part is the secret token. Without it, anyone could spam your endpoint and force your server to rebuild pages constantly, effectively DDOSing your own backend.

Step 2: Configure Your CMS Webhook

Now that the endpoint exists, you need to tell your CMS to call it. Whether you’re using headless WordPress with Next.js or Contentful, the process is similar:

Step 3: Handling Dynamic Paths

In a real-world scenario, you won’t hardcode the path. Your CMS usually sends the slug in the JSON body of the request. Here is how I modified my production implementation to handle dynamic slugs:

// Updated logic for dynamic slugs
const body = await request.json();
const slug = body.entry.slug; // Adjust based on your CMS payload structure
revalidatePath(`/blog/${slug}`);

Pro Tips for Production

After implementing on-demand ISR in several high-traffic projects, here are a few things I’ve learned:

Troubleshooting Common Issues

If your pages aren’t updating, check these three common culprits:

Issue Common Cause Solution
401 Unauthorized Token mismatch Double-check your .env variables and CMS webhook URL.
Page still stale CDN Caching Check if Vercel or Cloudflare is caching the HTML beyond the Next.js layer.
500 Internal Server Error Incorrect path format Ensure the path starts with a slash (e.g., /about not about).

What’s Next?

Now that you’ve mastered on-demand updates, you might be wondering if Next.js is always the right choice for your specific scale. If you find the complexity of ISR overkill for a purely content-driven site, you might want to explore Astro vs Next.js for static sites to see which fits your workflow better.