Incremental Static Regeneration in Next.js: An Architecture Note for Jamstack Sites
An architecture note on using Incremental Static Regeneration in a Next.js Jamstack site: when it fits, the minimal setup, trust boundaries, operational checks, and the failure modes that should trigger a redesign.
12 Jan 2026, 08:01 UTC

The problem this solves
A fully static Jamstack site is fast and cheap to serve, but every content change means a full rebuild. For a site with thousands of pages and an editorial team publishing several times a day, build times stretch into tens of minutes and editors wait. Incremental Static Regeneration (ISR) in Next.js removes that coupling: pages are generated at build time or on first request, then regenerated in the background after a configurable interval, while the CDN keeps serving the last good version. The takeaway: ISR is the right choice when content changes frequently but a few seconds-to-minutes of staleness is acceptable, and it is the wrong choice when every request must reflect the latest data.
Requirements
Before adopting ISR, confirm the following hold for your project:
- Next.js 12 or later. The
revalidateoption ingetStaticProps(Pages Router) or therevalidateexport /fetchoptions (App Router) does not exist in earlier versions. Check withnpx next --versionin your project root. - A hosting platform that persists a cache between requests. Vercel supports ISR natively. Self-hosted Node servers share only a per-instance in-memory cache unless you add a shared cache layer, which changes the design significantly.
- Tolerance for stale reads. A visitor may see content that is up to one revalidation interval old, plus regeneration time.
- A trusted content source. A headless CMS or internal API that your build and regeneration functions can authenticate against.
The smallest suitable design
Start with time-based revalidation on the pages that change, and nothing else. In the Pages Router that is one property:
// pages/posts/[slug].js — runs on your hosting platform, not the client
export async function getStaticProps({ params }) {
const post = await cms.getPost(params.slug); // authenticated server-side call
if (!post) return { notFound: true };
return {
props: { post },
revalidate: 60, // regenerate at most once per 60s when requested
};
}
export async function getStaticPaths() {
return { paths: [], fallback: 'blocking' };
}With fallback: 'blocking', pages not yet generated are rendered on demand on first request and then cached, so you do not need to build every page up front. The revalidate value is a ceiling on regeneration frequency, not a timer: a request arriving after the interval triggers a background regeneration while the stale page is still served.
Add on-demand revalidation only if editors cannot tolerate the interval. Expose a secret-protected API route (/api/revalidate) that calls res.revalidate('/posts/' + slug), and point a CMS webhook at it. The secret must live in an environment variable, never in the repository, because anyone with it can force regeneration and consume your quota.
Trust and data boundaries
ISR keeps the Jamstack trust model mostly intact: what the CDN serves is a static artifact, so a compromised visitor session cannot reach your CMS. The boundaries that matter are:
- CMS credentials are used only inside
getStaticPropsand API routes, which execute server-side. Never pass them into page props or client bundles. - The revalidation endpoint is a new attack surface. Treat it like any admin API: shared secret at minimum, and validate the webhook payload (e.g., check the slug is a real post) before calling
revalidate. - Personalized data has no place in an ISR page. Anything per-user (cart contents, auth state) must be fetched client-side after the static shell loads, or it will be cached and served to the wrong user.
Operational checks
After deploying, verify the behavior rather than assuming it:
- Publish a visible change in the CMS and wait longer than the revalidate interval.
- Request the page twice. The first request may return the stale version and trigger regeneration; the second should show the update. Inspect response headers — on Vercel,
x-vercel-cachevalues likeSTALEthenHITindicate the cycle worked. Header names differ by platform, so check your provider's documentation. - Watch build and function logs for regeneration errors. A failing
getStaticPropsdoes not take the page down; it silently keeps serving the old version. - If you use on-demand revalidation, test the webhook end to end once with a draft post and confirm the path being revalidated matches the deployed route.
Failure modes
- Regeneration throws. The CDN keeps serving the last successful page indefinitely. Staleness becomes unbounded unless you alert on repeated regeneration failures.
- CMS outage. Same effect: the site stays up on cached pages, which is usually a feature, but content freezes.
- Quota exhaustion. Regeneration counts against platform function invocations and, on some plans, has explicit limits. A webhook loop or a very low
revalidatevalue on high-traffic pages can burn through a free tier quickly. - Thundering herd on long intervals. Many distinct pages expiring at once can spike regeneration load after a deploy.
Conditions that would change the design
Revisit ISR if any of these become true: content must be current on every request (move to server-side rendering for those routes); the site is self-hosted across multiple Node instances (add a shared ISR cache or reconsider); revalidation volume threatens quotas (raise intervals, drop on-demand hooks for low-value pages, or batch updates); or pages need per-user data at render time (split into a static shell plus client-side fetching, or leave the Jamstack model for those views).
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.