Implementing Incremental Static Regeneration for a Next.js Product Page on Vercel
Learn how to add ISR to a Next.js product catalog so updates from a headless CMS propagate instantly without a full rebuild, using Vercel webhooks and API routes.
02 Sept 2025, 18:40 UTC

Desired Outcome
Deliver a product detail page that is served as static HTML on the first request, automatically regenerates in the background every revalidate seconds, and reflects any CMS update without triggering a full site rebuild.
Prerequisites
- Next.js 12+ – either the pages router or the new app router.
- Vercel account with a connected Git repository and the project deployed.
- Headless CMS (e.g., Contentful, Sanity) that can fire a webhook on content changes.
- Node.js 18+ installed locally for the build and API route.
- Environment variables:
VERCEL_ENV– set automatically by Vercel.WEBHOOK_SECRET– a random string used to validate the CMS webhook.
Focused Procedure
- Create the page component
- Pages router example –
pages/product/[slug].js:export async function getStaticProps({ params }) { const product = await fetchProductFromCMS(params.slug); return { props: { product }, revalidate: 60, // seconds }; } export async function getStaticPaths() { const slugs = await fetchAllProductSlugs(); return { paths: slugs.map(s => ({ params: { slug: s } })), fallback: 'blocking' }; } - App router example –
app/product/[slug]/page.js:export default async function ProductPage({ params }) { const product = await fetchProductFromCMS(params.slug); return ; } export const revalidate = 60; export const dynamic = 'force-dynamic';
- Pages router example –
- Set up the CMS webhook
- Configure the CMS to POST to
/api/revalidateon each publish, sending a JSON payload like{"slug":"awesome-widget"}. - Include the secret in the query string:
?secret=YOUR_SECRET.
- Configure the CMS to POST to
- Implement the revalidation API route
- Pages router –
pages/api/revalidate.js:export default async function handler(req, res) { const { secret, slug } = req.query; if (secret !== process.env.WEBHOOK_SECRET) { return res.status(401).end('Invalid secret'); } try { await res.revalidate(`/product/${slug}`); return res.json({ revalidated: true, slug }); } catch (err) { return res.status(500).json({ error: err.message }); } } - App router –
app/api/revalidate/route.js:import { NextResponse } from 'next/server'; export async function POST(req) { const { secret, slug } = await req.json(); if (secret !== process.env.WEBHOOK_SECRET) { return NextResponse.json({ error: 'unauthorized' }, { status: 401 }); } try { await res.revalidate(`/product/${slug}`); return NextResponse.json({ revalidated: true, slug }); } catch (err) { return NextResponse.json({ error: err.message }, { status: 500 }); } }
- Pages router –
- Deploy to Vercel
- Push code to Git; Vercel automatically runs
npm run build. - Verify that the
WEBHOOK_SECRETenvironment variable is set in the project settings.
- Push code to Git; Vercel automatically runs
Expected Checks
- Initial page load:
curl -I https://your-site.vercel.app/product/awesome-widget # Expect header: X‑Vercel‑Cache: HIT - After CMS update and webhook trigger:
curl -X POST -H 'Content-Type: application/json' \ -d '{"slug":"awesome-widget"}' \ https://your-site.vercel.app/api/revalidate?secret=YOUR_SECRET # Expect JSON: {"revalidated":true,"slug":"awesome-widget"} - Revalidate window test:
- Wait
revalidateseconds, request the page again. - Header should shift to
X‑Vercel‑Cache: STALEon the first request after the revalidate call, thenHITwith updated content.
- Wait
- No new deployment should appear in the Vercel dashboard after the webhook call; only the ISR invocation count increases.
Recovery Options
- Webhook secret mismatch – the API route will return 401. Check the
WEBHOOK_SECRETvalue in both the CMS configuration and Vercel environment variables. - ISR invocation limits reached – on the Hobby plan, 1,000 invocations per day are allowed. Use the Vercel dashboard to view the count and upgrade if necessary.
- Stale content beyond revalidate window – trigger on‑demand revalidation manually via the API route with a valid secret.
- Manual redeploy – if ISR fails repeatedly, run
vercel --prodfrom the project root to force a full rebuild.
Limitations & Practical Verification
- ISR on Vercel cannot exceed 31,536,000 seconds (1 year). If you set a longer interval, Vercel will cap it.
- Edge Functions (middleware) cannot trigger ISR directly; use a standard API route.
- Concurrent revalidation requests for the same path are deduplicated, but rapid bursts may hit Vercel rate limits. Monitor function logs for
revalidateerrors. - To verify the page is truly static, run
npm run buildlocally and inspect.next/server/pages/product/[slug].jsfor therevalidateproperty. - Use
curl -Iagainst a preview deployment to confirm cache headers before production.
Concrete Example: Updating a Product Title
Suppose the CMS changes the title of awesome-widget from "Widget A" to "Widget A+". The CMS posts the webhook. The API route revalidates the path. Within 60 seconds, a visitor sees the new title without a new deployment. If they visit immediately after the webhook, the X‑Vercel‑Cache: STALE header indicates the page is being regenerated in the background.
Conclusion
By following this procedure, a Jamstack site built with Next.js on Vercel can serve high‑performance static pages while still reflecting CMS updates on a fine‑grained schedule. The ISR mechanism keeps deployments lightweight and ensures content freshness without sacrificing the instant load times that static sites provide.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.