レンダリング

ISR (Incremental Static Regeneration)

ISR は「SSG + 期限切れ後のバックグラウンド再生成」のレンダリング戦略。Next.js が 9.5 (2020) で導入した用語。Nuxt (Nitro) の swr や SvelteKit の isr 設定等、他フレームワークでも類似機能が広がっている。

先に押さえる指標: FCP / LCP / TTI

この後のスイムレーン図には FCP / LCP / TTI のマイルストーンが登場する。それぞれが「ユーザー体験のどの瞬間」を測っているのかを先に整理しておくと、図の中で『なぜここが重要なのか』が腹落ちしやすい。

FCP First Contentful Paint — 最初の視覚コンテンツ描画
テキスト・画像・SVG・非空の Canvas のような「視覚的なコンテンツ」がブラウザ画面に最初に描画された時点。背景色のみの画面は含まれない。Core Web Vitals の補助指標で、1.8 秒以内が良好とされる。
LCP Largest Contentful Paint — 最大コンテンツ描画
ページ内で最大のコンテンツ要素 (ヒーロー画像・主要見出し等) が描画された時点。「意味のある初回描画」の代理指標で、Core Web Vitals の主指標。2.5 秒以内が良好、4 秒以上は要改善。
TTI Time to Interactive — 操作可能になった時刻
ページが完全にインタラクティブ (クリックや入力に反応できる) になった時点。全てのイベントリスナーが付与され、main thread が 5 秒間 Long Task を出さなくなるタイミング。2024 年に Core Web Vitals 主指標から外れ INP に置き換わったが、実ユーザーの「使えるようになる時刻」として依然重要。

ISR の概論

ISR (Incremental Static Regeneration) は「SSG の静的生成を基本としつつ、期限切れ後にバックグラウンドで HTML を再生成する」レンダリング戦略。Next.js 9.5 (2020) で「Incremental Static Regeneration」として命名され、以降 Nuxt / Nitro の swr、SvelteKit の isr 等、他フレームワークでも類似機能が広がった。Next.js 公式ドキュメントでは ISR の動作を次のように説明している:

ISR (Incremental Static Regeneration) を使うと次のことが可能になります:

  • サイト全体を再ビルドせずに静的コンテンツを更新できる
  • 大半のリクエストに対して事前レンダリングされた静的ページを配信することで、サーバー負荷を軽減できる
  • 適切な cache-control ヘッダーがページに自動的に付与される
  • next build の実行時間を長くすることなく、大量のコンテンツページを扱える
Next.js Documentation (MIT) より。英語原文をサイト独自に和訳。

つまり ISR は、純粋な SSG の「更新のたびに全サイト再ビルド」と、SSR の「毎リクエスト再レンダリング」のちょうど中間にある。普段は SSG 寄りの挙動 (CDN 配信でサーバー負荷極小) を取りながら、必要なときだけ部分的に再生成する、というハイブリッド戦略。

レンダリングライフサイクル

ビルド時のフェーズは SSG と完全に同じ (B1-B3)。ISR の真骨頂はランタイムで、期限内は CDN から static HTML を即返却、期限切れ後の最初のアクセスで「stale を即返却 + バックグラウンド再生成」の stale-while-revalidate 挙動が発動する (R3-R6)。on-demand 系 (R7) は webhook 起点で任意のパスをその場で無効化できる。

ビルド時 (SSG と共通)

通常の SSG と同じ 3 フェーズ。ここで全ページを HTML 化して CDN に配置する。

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

    npm run build 実行

    B1 Developer / CI npm run build 実行

    SSG と同じく、CI やローカルで全ルートをビルド。Next.js なら generateStaticParams で列挙したパス全てを事前生成する。

  2. B2

    全ルート分の HTML を事前生成

    B2 Build (Node.js) 全ルート分の HTML を事前生成

    各ルートで fetch などを叩いてデータを取得し、renderToString で HTML を完成させる。Next.js 公式曰く「next build の間に、既知の全ブログ投稿が生成される」。

  3. B3

    静的 HTML を CDN / Edge cache に配置

    B3 CDN / Edge Cache 静的 HTML を CDN / Edge cache に配置

    出来上がった HTML を Vercel Edge / Cloudflare / 自前 CDN 等にデプロイ。あわせて TTL (revalidate = 60 等) を cache メタデータに付与する。

  4. ここまでは通常の SSG と完全に同じ
ランタイム (ISR 固有ロジック)

R1-R2 は SSG と同じ。R3-R6 が time-based revalidation の核心。R7 は on-demand revalidation で、CMS 公開後の即時反映ユースケースに対応する別経路。

User
Network
CDN / Edge Cache
Regenerator
Browser
  1. R1

    初回アクセス (期限内)

    R1 User 初回アクセス (期限内)

    ビルド済みの HTML がそのまま CDN エッジから返却される。通常の SSG と全く同じ経路。TTFB / FCP は極めて速い。

  2. R2

    cache HIT → static HTML 返却

    R2 CDN / Edge Cache cache HIT → static HTML 返却

    CDN は cache HIT を確認して即時応答。Next.js は x-nextjs-cache: HIT のレスポンスヘッダーを付ける (公式ドキュメント記載)。

  3. R3

    期限切れ後のアクセス (期限切れ + 1 回目)

    R3 User 期限切れ後のアクセス (期限切れ + 1 回目)

    revalidate: 60 の場合、ビルドから 60 秒以上経った時の最初のアクセス。ここで ISR の核心ロジックが発動する。

  4. R4

    stale を即座に返しつつバックグラウンドで再生成トリガー

    R4 CDN / Edge Cache stale を即座に返しつつバックグラウンドで再生成トリガー

    Next.js 公式曰く「60 秒経過後の次のリクエストは、キャッシュ済み (もう古くなった) ページをそのまま返す。キャッシュは無効化され、新しいバージョンのページがバックグラウンドで生成され始める」。ユーザーは古い HTML を即座に受け取れる = 体感は速い。レスポンスヘッダーは x-nextjs-cache: STALE

  5. R5

    バックグラウンドで再レンダリング

    R5 Regenerator バックグラウンドで再レンダリング

    サーバー (or Edge) が最新データで HTML を再生成。このプロセスはユーザーの応答待ちをブロックしない。課金モデルによっては、このバックグラウンド処理も別途計算時間としてカウントされる点に注意。

  6. R6

    cache 更新 → 以降のアクセスに新 HTML を配信

    R6 CDN / Edge Cache cache 更新 → 以降のアクセスに新 HTML を配信

    再生成が完了すると CDN cache が新しい HTML に差し替わる。次のアクセスからは x-nextjs-cache: HIT で新バージョンが返る。

  7. 時間ベース (revalidate) の ISR サイクル完了
  8. R7

    (別系統) on-demand revalidation

    R7 Regenerator (別系統) on-demand revalidation

    Next.js の revalidatePath('/blog')revalidateTag('posts') を Server Action / Webhook 経由で呼び出すと、期限切れを待たずに特定の cache をその場で無効化できる。典型例: CMS で記事を更新 → Webhook → revalidate。次のアクセスは即 MISS → 再生成 → 新 HTML。

  9. CMS 公開直後でも即時反映できる

Next.js ISR (主要 API)

「ISR」という用語は Next.js が命名した機能名なので、本家の API が最も体系的に整っている。主要な指定方法と運用上のヘッダーを整理する。

export const revalidate = 60
ページ単位で time-based revalidation を有効化。60 秒の TTL。Next.js 公式の最小例でも使われる最も基本的な ISR 指定。
revalidatePath('/blog/1')
on-demand revalidation (パス指定)。Server Action や Route Handler から呼び出して、そのパスの cache を無効化。Next.js 公式の推奨は「ほとんどのユースケースではパス全体の revalidate を優先せよ」。
revalidateTag('posts')
on-demand revalidation (タグ指定)。fetch 時に next: { tags: ['posts'] } で付けたタグに紐付くキャッシュ全てを無効化。より細粒度なコントロール用。
x-nextjs-cache: HIT / STALE / MISS / REVALIDATED
Next.js がレスポンスに付けるキャッシュ状態ヘッダー。HIT = キャッシュから提供、STALE = キャッシュから提供しつつバックグラウンド再生成中、MISS = キャッシュに無く都度生成、REVALIDATED = on-demand で再生成された。運用時のデバッグに必須。

他フレームワークの ISR 相当機能

「ISR」という名前は Next.js 固有だが、同じ概念 (静的生成 + バックグラウンド再生成 = HTTP の stale-while-revalidate) は主要フレームワークで利用可能。用語と設定方法が異なるだけ。以下、各フレームワークの対応状況。

Nuxt (Nitro)

routeRules swr / isr

Nitro の routeRulesswr (stale-while-revalidate) または isr オプションを指定できる。Nuxt は「SWR」の呼称を推奨しているが、機能的には Next.js の ISR と同等。nativeSWR 設定を有効化すると Netlify / Vercel の native キャッシュレイヤーを使い、無効だと ISR 挙動にフォールバックする。

// 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 本体には ISR 機能は無いが、Vercel adapter 経由で ISR を利用できる。expiration (TTL 秒数、必須)、bypassToken (on-demand 無効化用のトークン)、allowQuery (キャッシュキーに含めるクエリパラメータ) を指定。

// +page.js (or +page.server.js)
export const config = {
  isr: {
    expiration: 60,
    bypassToken: BYPASS_TOKEN,  // 32 文字以上
    allowQuery: ['search']
  }
};

Astro

組込 ISR 機能なし

Astro 公式ドキュメントにも「ISR」の用語は登場しない。組込の機能としては SSG (prerender) か on-demand rendering (SSR) の二択で、両者の中間にあたる ISR 的パターンは標準では提供されていない。必要なら Astro.response.headers.set('Cache-Control', 'public, max-age=3600, stale-while-revalidate=60') を手動で設定し、CDN 側で HTTP 標準の SWR に任せるのが一般的。

// 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 (現 React Router) には専用の ISR 機能は無く、HTTP の Cache-Control ヘッダーで s-maxage + stale-while-revalidate を設定するのが定石。CDN (Cloudflare / Vercel Edge / CloudFront) が HTTP 仕様に従って ISR 相当の挙動を実現する形。「フレームワーク機能」ではなく「HTTP プロトコルレベル」で同じことをするアプローチ。

// loader.ts
export const loader = async () => {
  return new Response(JSON.stringify(data), {
    headers: {
      'Cache-Control':
        'public, s-maxage=60, stale-while-revalidate=600',
    },
  });
};

よくある疑問

ISR / SSG / SSR の位置関係や、運用上ハマりやすい挙動を整理。

Q. ISR と SSG の違いは?
ISR は SSG の拡張。ベースは SSG (ビルド時に全ページを静的生成 → CDN 配信) と同じで、「ページ単位で後から自動更新できる」仕組みを上に乗せた形。純粋な SSG では記事を 1 つ更新するたびに全サイトを再ビルドする必要があるが、ISR なら該当ページだけを (時間経過後 or 明示的な webhook で) 再生成できる。数万ページ規模のサイトで「毎度再ビルドは現実的じゃないが、定期的に鮮度を保ちたい」というユースケースが典型。
Q. ISR と SSR の違いは?
SSR は毎リクエスト再レンダリング、ISR は期限切れ or webhook 時のみ再レンダリング。SSR はサーバーコストがリクエスト数に比例するが、ISR は圧倒的多数のアクセスを CDN cache で捌き、サーバーでの計算は期限切れ時と on-demand 時だけ。よく「SSR と SSG の中間」と言われるが、実態としては 『SSG ベースで、必要に応じて SSR を呼ぶ』 ハイブリッドで、普段は SSG 寄りの挙動。
Q. なぜ Next.js 以外では『ISR』と呼ばれないことが多い?
「ISR」という名前は Next.js (Vercel) が 2020 年に 9.5 で命名した固有名。技術実態は HTTP の stale-while-revalidate という RFC 5861 で標準化されている仕組みそのもので、Nuxt / Nitro はこの HTTP 語彙に寄せて swr という名前を採用。SvelteKit の Vercel adapter はプラットフォームに合わせて isr を採用。つまり 「ISR」は Vercel / Next.js エコシステムの用語、他では同じ概念を HTTP 標準に従って swr と呼ぶことが多い、というだけ。仕組み自体は共通。
Q. `revalidate = 0` や `dynamic` を指定するとどうなる?
Next.js 公式曰く「ルートで使われている fetch リクエストのいずれかで revalidate0 または明示的に no-store になっている場合、そのルートは動的にレンダリングされる」。つまり ISR ではなく SSR (dynamic rendering) に切り替わる。コードを「ちょっと動的にしたい」つもりで revalidate = 0 を書くと、期待と違って毎リクエストでフルレンダリングされ、CDN キャッシュも効かないことに注意。真の動的ページは export const dynamic = 'force-dynamic' で明示する方が意図が伝わる。
Q. 複数インスタンスでキャッシュは同期される?
デフォルトでは 同期されない。Next.js 公式 Caveats 曰く「複数インスタンスで実行する場合、デフォルトのファイルシステムキャッシュはインスタンスごとに分離されている。on-demand revalidation は呼び出しを受けたインスタンスだけを無効化する」。つまり 3 つのサーバーインスタンスで運用していて、そのうちの 1 つに webhook が届いて revalidate した場合、他 2 つは古いキャッシュのまま。Redis 等の共有ストレージを incrementalCacheHandlerPath で指定する必要がある。Vercel にデプロイした場合は Vercel 側が裏で共有キャッシュを管理してくれる。
Q. ISR が向いているケース / 向かないケースは?
向いている: (a) コンテンツ中心で、ビルド時に全ページを生成したいが、ページ数が多くて更新ごとの再ビルドは非現実的なサイト (大規模ブログ / EC / ドキュメントサイト) — Next.js 公式も「Handle large amounts of content pages without long next build times」を ISR の主眼に挙げている。向かない: (a) ユーザーごとに違う内容 (ログイン後ダッシュボード等) — 共有 CDN cache に載せられない。(b) Next.js 公式が「If you need real-time data, consider switching to dynamic rendering」と明言している通り、リアルタイムデータが必要なケースは dynamic rendering (SSR) / streaming / WebSocket。(c) 更新頻度が低くビルドで事足りるサイト — 素の SSG で十分。

設計判断のサイン

ISR を採用すべきケースと、他の戦略のほうが素直なケース。

ISR が適切

ページ数が多い大規模コンテンツサイト

数万〜数十万ページ規模で、毎回の再ビルドが現実的でない大規模ブログ / EC / ニュース / ドキュメントサイト。ISR なら変更があったページだけを自動で鮮度を保てる。

ISR が適切

CMS 連携で「公開後すぐ反映」が欲しい

ヘッドレス CMS (Contentful / microCMS / Sanity 等) から公開イベントの Webhook を受け取って revalidatePath を呼び出せば、該当ページだけ即座に新版に切り替えられる。全サイト再ビルドは不要。

ISR が適切

トラフィック変動が大きく、サーバー負荷を予測したくない

急激にアクセスが増えても、期限内であれば大半のリクエストは CDN cache HIT で捌かれる。Next.js 公式が「Reduce server load by serving prerendered, static pages for most requests」と述べている通り、サーバー処理が発生するのは期限切れ時と on-demand 時のみ。毎リクエスト計算する SSR と比べてサーバー側コストの性質が異なる。

ISR は向かない

ユーザーごとにページ内容が違う

ログイン後ダッシュボードのような 1 ユーザー 1 ページの内容は、CDN の共有キャッシュに載せられない。ISR の本領は「全員同じ HTML」。個別内容は SSR or CSR で取得する。

ISR は向かない

リアルタイム性が必須のデータ

Next.js 公式の推奨は「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」。リアルタイムデータが必要なユースケース (株価 / ライブスポーツ / コラボエディタ等) では、公式が示すとおり dynamic rendering (SSR) や streaming / WebSocket / SSE を選ぶほうが素直。

ISR は向かない

完全に静的で更新頻度が非常に低い

更新頻度が低くビルドが現実的なサイト (企業サイト・LP 等) は、素の SSG でも「ビルド時再生成」で十分にカバーできる。ISR の差分は「ビルドを待たずに自動更新する機構」なので、そもそも自動更新が不要なサイトでは ISR 固有のメリットが得られない。

よくある落とし穴

stale-while-revalidate の伝搬遅延を勘違いする

期限切れ後の 最初のアクセス は古い HTML が返る (これが SWR の仕様)。つまりユーザー A が期限切れ直後にアクセスすると古い内容を受け取り、バックグラウンドで再生成 → ユーザー B 以降に新しい内容が見える、という挙動。「revalidate 経過したら即新しい」と誤解していると、QA でステージング環境から本番確認するときに混乱する。Next.js 公式にも明記されている通り「次のリクエストは依然としてキャッシュされた (もう古くなった) ページを返す」。

複数インスタンス運用で cache が同期されない

Node.js サーバーを 3 台で運用し、CMS の Webhook が 1 台にだけ届いて revalidatePath した場合、他の 2 台はまだ古い cache を返す。対策: (a) Redis 等の共有ストレージを incrementalCacheHandlerPath で指定、(b) Vercel のようなマネージドプラットフォームに載せる、(c) CDN 層で明示的に purge API を叩く。セルフホスティングで最も陥りやすいワナの 1 つ。

Proxy / middleware が on-demand ISR に適用されない

Next.js 公式 Caveats 曰く「on-demand ISR リクエストでは Proxy が実行されない。つまり Proxy 内のパス書き換えやロジックは適用されない。revalidate は正確なパス (例: 書き換え後の /post-1 ではなく /post/1) を指定すること」。URL 書き換えを middleware でやっている場合は、revalidate 対象のパスを書き換え の正確なパスで指定する必要がある。

バックグラウンド再生成のコストを見落とす

Next.js 公式曰く「バックグラウンド再生成 (stale-while-revalidate) は、トリガーとなるリクエストを受けたインスタンス上で実行される。リクエストごと課金のプラットフォームでは、このバックグラウンド処理は追加の計算として課金対象になる」。Vercel の Function 実行時間など従量課金のプラットフォームでは、ユーザーに返したあとも裏で動き続ける計算時間が請求対象になる。無料枠を超える可能性がある点を意識する。