ISR (Incremental Static Regeneration)
ISR is a rendering strategy combining SSG with background regeneration after expiration. The term was coined by Next.js 9.5 (2020). Similar features now exist across frameworks — Nuxt (Nitro) swr, SvelteKit isr, and so on.
Metrics Primer: FCP / LCP / TTI
The swim-lane diagram below marks FCP / LCP / TTI milestones. Knowing what each metric actually measures upfront makes the diagram much easier to read.
- FCP First Contentful Paint — first visual content painted
- The moment text, images, SVG, or non-empty canvas is first painted on the browser viewport. A background-color-only screen doesn't count. A Core Web Vitals secondary metric — under 1.8 s is considered good.
- LCP Largest Contentful Paint — largest content painted
- When the largest content element (hero image, big heading, etc.) is painted. A proxy for 'meaningful first paint' and a primary Core Web Vitals metric. Under 2.5 s is good; over 4 s needs work.
- TTI Time to Interactive — when interactivity starts
- The moment the page is fully interactive — all event listeners attached and the main thread free of Long Tasks for 5 seconds. Dropped from the Core Web Vitals primary set (replaced by INP) in 2024, but still important as the 'feels usable' moment for real users.
What ISR Is
Incremental Static Regeneration (ISR) is a rendering strategy that pre-generates HTML like SSG but also refreshes pages in the background after TTL expiry. The term was coined by Next.js 9.5 (2020); similar features have since spread across frameworks — Nuxt / Nitro's `swr`, SvelteKit's `isr`, etc. The official Next.js docs describe ISR's behavior as follows:
Incremental Static Regeneration (ISR) enables you to:
— Next.js Documentation (MIT)
- Update static content without rebuilding the entire site
- Reduce server load by serving prerendered, static pages for most requests
- Ensure proper
cache-controlheaders are automatically added to pages- Handle large amounts of content pages without long
next buildtimes
In other words, ISR sits between pure SSG ('rebuild the whole site for every update') and SSR ('re-render on every request'). It behaves like SSG most of the time (CDN-served, near-zero server load) while regenerating selectively when needed — a hybrid strategy.
Rendering Lifecycle
The build-time phases (B1-B3) are identical to SSG. ISR's essence lives at runtime: within TTL the CDN serves static HTML instantly; after TTL expires, the first request triggers stale-while-revalidate behavior — serving stale content immediately while regenerating in the background (R3-R6). The on-demand path (R7) lets a webhook invalidate any path on the spot.
Same three phases as standard SSG. Every page is rendered to HTML and placed on the CDN.
-
B1 Run npm run build
As with SSG, CI or local build emits every route. In Next.js,
generateStaticParamsenumerates the paths to pre-generate. -
B2 Pre-render HTML for every route
Each route fetches data and uses renderToString to produce HTML. Per Next.js docs: 'During
next build, all known blog posts are generated.' -
B3 Place static HTML into the CDN / Edge cache
Deploy the generated HTML to Vercel Edge / Cloudflare / your own CDN, along with TTL metadata (e.g.,
revalidate = 60) in the cache header. - Up to here, identical to a vanilla SSG build
R1-R2 match SSG. R3-R6 is the heart of time-based revalidation. R7 is on-demand revalidation — a separate path for near-instant propagation after CMS publishes.
-
R1 First access (within TTL)
The pre-built HTML is served straight from the CDN edge — exactly like vanilla SSG. TTFB / FCP are extremely fast.
-
R2 Cache HIT → serve static HTML
The CDN hits the cache and responds instantly. Next.js attaches the response header
x-nextjs-cache: HIT(per official docs). -
R3 Access after TTL expires (first request past expiry)
With
revalidate: 60, this is the first request more than 60 seconds after build. This is where ISR's core logic kicks in. -
R4 Serve stale immediately + trigger background regeneration
Next.js docs: 'After 60 seconds has passed, the next request will still return the cached (now stale) page. The cache is invalidated and a new version of the page begins generating in the background.' The user gets the stale HTML instantly — it feels fast. Response header:
x-nextjs-cache: STALE. -
R5 Re-render in the background
The server (or Edge) re-renders HTML with the latest data. This does not block the user's response. Note: per-request billing platforms count this background work as additional compute.
-
R6 Cache updated → subsequent requests get fresh HTML
When regeneration completes, the CDN cache swaps to the new HTML. Subsequent requests receive the fresh version with
x-nextjs-cache: HIT. - Time-based ISR cycle complete
-
R7 (Separate path) on-demand revalidation
Calling Next.js
revalidatePath('/blog')orrevalidateTag('posts')from a Server Action / Webhook invalidates specific cache entries without waiting for TTL. Typical pattern: publish a CMS article → webhook → revalidate. The next request goes straight to MISS → regen → new HTML. - Enables near-instant propagation after a CMS publish
Next.js ISR (Key APIs)
Since 'ISR' is the name Next.js coined, its APIs provide the most systematic coverage. Here are the key declarations and operational headers.
export const revalidate = 60- Enables time-based revalidation on a per-page basis. 60-second TTL. The most basic ISR declaration — also the form used in Next.js's minimal example.
revalidatePath('/blog/1')- On-demand revalidation by path. Call from a Server Action or Route Handler to invalidate that path's cache. Next.js recommends 'For most use cases, prefer revalidating entire paths.'
revalidateTag('posts')- On-demand revalidation by tag. Invalidates all caches associated with a tag that was attached at fetch time via
next: { tags: ['posts'] }. For finer-grained control. x-nextjs-cache: HIT / STALE / MISS / REVALIDATED- Response header attached by Next.js indicating cache state.
HIT= served from cache,STALE= served from cache while revalidating in background,MISS= not in cache, rendered fresh,REVALIDATED= regenerated via on-demand revalidation. Essential for operational debugging.
Equivalent Features in Other Frameworks
The name 'ISR' is Next.js-specific, but the same concept (static generation + background regeneration = HTTP's stale-while-revalidate) is available across major frameworks with different terminology and syntax. Here's the support landscape.
Nuxt (Nitro)
routeRules swr / isrNitro's routeRules supports swr (stale-while-revalidate) or isr options. Nuxt prefers the 'SWR' label, but functionally matches Next.js ISR. Enabling the nativeSWR flag uses Netlify / Vercel's native cache layer; disabling it falls back to ISR behavior.
// nuxt.config.ts
export default defineNuxtConfig({
routeRules: {
'/blog/**': { swr: 600 }, // 600s TTL
'/static/**': { isr: 3600 }, // ISR で 1 時間キャッシュ
'/admin/**': { ssr: false }, // SPA モード
},
}) SvelteKit (Vercel adapter)
export const config.isrSvelteKit itself does not ship ISR, but the Vercel adapter enables it. Specify expiration (TTL in seconds, required), bypassToken (token for on-demand invalidation), and allowQuery (query params to include in the cache key).
// +page.js (or +page.server.js)
export const config = {
isr: {
expiration: 60,
bypassToken: BYPASS_TOKEN, // 32 文字以上
allowQuery: ['search']
}
}; Astro
組込 ISR 機能なしAstro's official documentation never uses the term 'ISR'. Built-in choices are SSG (prerender) or on-demand rendering (SSR); the middle-ground ISR pattern isn't provided out of the box. The common workaround is manually setting Astro.response.headers.set('Cache-Control', 'public, max-age=3600, stale-while-revalidate=60') and letting the CDN handle HTTP-standard SWR semantics.
// Astro の on-demand page
export const prerender = false;
// ---
// <script> 的な部分で:
Astro.response.headers.set(
'Cache-Control',
'public, max-age=3600, stale-while-revalidate=60'
); Remix / React Router
HTTP Cache-ControlRemix (now React Router) has no dedicated ISR feature. The idiomatic approach is to set HTTP Cache-Control with s-maxage + stale-while-revalidate — the CDN (Cloudflare / Vercel Edge / CloudFront) then implements ISR-equivalent behavior per HTTP spec. Same result, achieved at the protocol level rather than as a framework feature.
// loader.ts
export const loader = async () => {
return new Response(JSON.stringify(data), {
headers: {
'Cache-Control':
'public, s-maxage=60, stale-while-revalidate=600',
},
});
}; Frequently Asked Questions
Clarifying the positions of ISR / SSG / SSR and the operational quirks that tend to surprise people.
Q. What's the difference between ISR and SSG?
Q. What's the difference between ISR and SSR?
Q. Why do other frameworks rarely call it 'ISR'?
swr; SvelteKit's Vercel adapter uses isr because it targets that platform. In short: 'ISR' is Vercel / Next.js ecosystem terminology; other frameworks call the same concept swr following the HTTP standard. The mechanism itself is common.Q. What happens when I set `revalidate = 0` or `dynamic`?
fetch requests used on a route have a revalidate time of 0, or an explicit no-store, the route will be dynamically rendered.' In other words, ISR is abandoned and the route becomes SSR (dynamic rendering). Writing revalidate = 0 intending 'a bit more dynamic' unexpectedly causes full per-request rendering with no CDN caching. If you really want fully dynamic, export const dynamic = 'force-dynamic' makes the intent explicit.Q. Are caches synchronized across multiple instances?
incrementalCacheHandlerPath. On Vercel's platform, Vercel manages shared caching for you.Q. When is ISR a good fit — and when isn't it?
next build times' as a primary goal of ISR. Poor fit: (a) Per-user personalization (post-login dashboards) — can't live in shared CDN cache. (b) As Next.js docs state: 'If you need real-time data, consider switching to dynamic rendering' — real-time cases need dynamic rendering (SSR) / streaming / WebSocket. (c) Sites with rare updates where rebuilding is sufficient — plain SSG is enough.Design Signals
When ISR is the right choice — and when another strategy is cleaner.
Large content sites with many pages
Sites with tens or hundreds of thousands of pages where full rebuilds aren't practical — large blogs / e-commerce / news / docs. ISR keeps only the touched pages fresh automatically.
CMS-backed and 'publish-now' freshness required
Receive a publish webhook from a headless CMS (Contentful / microCMS / Sanity) and call revalidatePath to swap just that page to the latest version. No full-site rebuild needed.
Unpredictable traffic spikes; avoid per-request server cost
When traffic spikes, most requests are served from the CDN cache as long as they're within TTL. As Next.js docs note, ISR lets you 'Reduce server load by serving prerendered, static pages for most requests' — server computation only fires on expiry or on-demand. This gives a fundamentally different server-side cost shape than SSR, which computes on every request.
Per-user page content
Logged-in dashboards, where each user sees different content, can't live in a shared CDN cache. ISR's strength is 'same HTML for everyone'. Per-user content should be fetched via SSR or CSR.
Real-time data required
Next.js docs recommend: 'We recommend setting a high revalidation time. For instance, 1 hour instead of 1 second. If you need more precision, consider using on-demand revalidation. If you need real-time data, consider switching to dynamic rendering.' Per the docs, real-time use cases (stock quotes, live sports, collaborative editors) are better served by dynamic rendering (SSR), streaming, WebSocket, or SSE.
Fully static with very rare updates
For sites that update rarely enough to rebuild comfortably (corporate sites, landing pages), plain SSG's 'rebuild-on-deploy' model is sufficient. ISR's differentiator is 'automatic update without waiting for a rebuild', so sites that don't need automatic updates don't benefit from ISR's specific advantages.
Common Pitfalls
Misunderstanding stale-while-revalidate propagation
The first request after TTL expiry still returns the stale HTML (this is SWR's spec). User A hits right after expiry and gets the old content; regeneration happens in the background; user B and onward see the fresh content. Assuming 'after revalidate, instantly fresh' causes confusion during QA handoff from staging. Per Next.js docs: 'next request will still return the cached (now stale) page.'
Multi-instance deployments don't auto-sync caches
With three Node.js instances, if only one receives the CMS webhook and calls revalidatePath, the other two keep serving stale. Fixes: (a) point incrementalCacheHandlerPath at shared storage like Redis, (b) deploy to a managed platform like Vercel, (c) explicitly hit the CDN's purge API. A common self-hosting pitfall.
Proxy / middleware doesn't apply to on-demand ISR
Per Next.js caveats: 'Proxy won't be executed for on-demand ISR requests, meaning any path rewrites or logic in Proxy will not be applied. Ensure you are revalidating the exact path. For example, /post/1 instead of a rewritten /post-1.' If you rewrite URLs in middleware, target the rewritten (actual) path when calling revalidate.
Missing the cost of background regeneration
Per Next.js docs: 'Background regeneration (stale-while-revalidate) runs on the instance that receives the triggering request. On platforms with per-request billing, this background work counts as additional compute.' On pay-per-execution platforms like Vercel Functions, the compute that continues after responding to the user is billed. Watch for unexpected free-tier overages.