Rendering

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:

  • Update static content without rebuilding the entire site
  • Reduce server load by serving prerendered, static pages for most requests
  • Ensure proper cache-control headers are automatically added to pages
  • Handle large amounts of content pages without long next build times
Next.js Documentation (MIT)

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.

Build time (shared with SSG)

Same three phases as standard SSG. Every page is rendered to HTML and placed on the CDN.

Developer / CI
Build (Node.js)
CDN / Edge Cache
  1. B1

    Run npm run build

    B1 Developer / CI Run npm run build

    As with SSG, CI or local build emits every route. In Next.js, generateStaticParams enumerates the paths to pre-generate.

  2. B2

    Pre-render HTML for every route

    B2 Build (Node.js) 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.'

  3. B3

    Place static HTML into the CDN / Edge cache

    B3 CDN / Edge Cache 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.

  4. Up to here, identical to a vanilla SSG build
Runtime (ISR-specific logic)

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.

User
Network
CDN / Edge Cache
Regenerator
Browser
  1. R1

    First access (within TTL)

    R1 User First access (within TTL)

    The pre-built HTML is served straight from the CDN edge — exactly like vanilla SSG. TTFB / FCP are extremely fast.

  2. R2

    Cache HIT → serve static HTML

    R2 CDN / Edge Cache 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).

  3. R3

    Access after TTL expires (first request past expiry)

    R3 User 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.

  4. R4

    Serve stale immediately + trigger background regeneration

    R4 CDN / Edge Cache 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.

  5. R5

    Re-render in the background

    R5 Regenerator 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.

  6. R6

    Cache updated → subsequent requests get fresh HTML

    R6 CDN / Edge Cache 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.

  7. Time-based ISR cycle complete
  8. R7

    (Separate path) on-demand revalidation

    R7 Regenerator (Separate path) on-demand revalidation

    Calling Next.js revalidatePath('/blog') or revalidateTag('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.

  9. 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 / isr

Nitro'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.isr

SvelteKit 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-Control

Remix (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?
ISR is an extension of SSG. The base is the same — SSG pre-builds every page and serves from a CDN. ISR adds 'each page can be auto-refreshed later'. Pure SSG requires a full-site rebuild to update one article, while ISR regenerates just that page (after TTL or via explicit webhook). Typical use case: sites with tens of thousands of pages where full rebuilds are impractical but freshness still matters.
Q. What's the difference between ISR and SSR?
SSR re-renders on every request; ISR re-renders only on expiry or webhook. SSR's server cost scales with request count, while ISR handles the vast majority of traffic from the CDN and only computes on expiry or on-demand. It's often described as 'between SSR and SSG', but in practice it's 'SSG-based, with SSR called only when needed' — a hybrid that behaves closer to SSG most of the time.
Q. Why do other frameworks rarely call it 'ISR'?
The name 'ISR' was coined by Next.js (Vercel) with version 9.5 in 2020. The underlying mechanism is HTTP's stale-while-revalidate, standardized as RFC 5861. Nuxt / Nitro adopts the HTTP-side vocabulary 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`?
Per Next.js docs: 'If any of the 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?
By default, no. Next.js caveats: 'When running multiple instances, the default file-system cache is per-instance. On-demand revalidation only invalidates the instance that receives the call.' So with three server instances, if a webhook hits only one and calls revalidate, the other two keep serving stale. You must configure shared storage (e.g., Redis) via incrementalCacheHandlerPath. On Vercel's platform, Vercel manages shared caching for you.
Q. When is ISR a good fit — and when isn't it?
Good fit: (a) Content-heavy sites with too many pages to rebuild on every update (large blogs / e-commerce / docs). Next.js official docs call out 'Handle large amounts of content pages without long 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.

ISR fits here

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.

ISR fits here

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.

ISR fits here

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.

ISR doesn't fit

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.

ISR doesn't fit

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.

ISR doesn't fit

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.