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:
- A Next.js project (v13+ using the App Router is recommended, though this works for Pages Router too).
- A headless CMS or a database that can send HTTP POST requests (webhooks).
- Basic familiarity with API routes in Next.js.
Step-by-Step Implementation
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:
- Go to your CMS Webhooks settings.
- Set the payload URL to:
https://your-domain.com/api/revalidate?secret=YOUR_TOKEN&path=/blog/my-post. - Select the trigger event (e.g., “Post Published” or “Post Updated”).
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:
- Use revalidateTag: If a single piece of data (like a global footer) appears on 100 pages, don’t use
revalidatePath100 times. Instead, tag your fetch requests withnext: { tags: ['footer'] }and userevalidateTag('footer')to update all of them at once. - Logging: Always log your revalidation requests. It’s a nightmare to debug why a page isn’t updating if you don’t know if the webhook even fired.
- Avoid Over-revalidation: If your CMS triggers a webhook for every single character change in an auto-save draft, your build queue will explode. Set your webhooks to fire only on “Publish”.
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.