# 機能設計書 36-Incremental Static Regeneration（ISR）

## 概要

本ドキュメントは、Next.jsにおけるIncremental Static Regeneration（ISR）機能の設計を記述する。ISRはデプロイ後に静的ページを段階的に再生成する機能であり、revalidateオプションで制御される。ビルド時の静的生成とリクエスト時の動的生成を組み合わせたハイブリッドアプローチを提供する。

### 本機能の処理概要

**業務上の目的・背景**：大規模なECサイトやCMSベースのサイトでは、全ページをビルド時に生成するのは時間がかかりすぎる場合がある。ISRはビルド後にも静的ページの再生成を可能にし、コンテンツの鮮度とパフォーマンスを両立させる。stale-while-revalidateパターンにより、ユーザーには即座にキャッシュ済みページを返しつつ、バックグラウンドで最新データでの再生成を行う。

**機能の利用シーン**：ECサイトの商品ページ（在庫状況の更新）、ブログ記事（コンテンツ更新の反映）、ダッシュボード（定期的なデータ更新）など、頻繁なビルドなしにコンテンツを最新に保ちたい場面で利用される。

**主要な処理内容**：
1. IncrementalCacheによるキャッシュ管理（メモリキャッシュ、ファイルシステムキャッシュ）
2. revalidate時間に基づくキャッシュ有効期限の管理
3. stale-while-revalidateによるバックグラウンド再生成
4. オンデマンド再検証（revalidatePath / revalidateTag）
5. タグベースのきめ細かいキャッシュ無効化

**関連システム・外部連携**：IncrementalCache、ResponseCache、CacheHandler（カスタムキャッシュバックエンド）、SharedCacheControls。

**権限による制御**：オンデマンド再検証にはPRERENDER_REVALIDATE_HEADERによるpreviewModeIdの検証が必要。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 6 | グローバルエラーページ | 参照画面 | HandleISRErrorコンポーネントによるISRエラー時の再検証処理 |

## 機能種別

キャッシュ管理 / バックグラウンド再生成 / データ取得

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| revalidate | number \| false | Yes | 再検証間隔（秒）。falseの場合は再検証なし | 正の整数またはfalse |
| cacheKey | string | Yes | キャッシュエントリのキー（通常はページパス） | - |
| tags | string[] | No | キャッシュタグ（タグベース無効化用） | - |
| requestHeaders | Record | Yes | リクエストヘッダー | - |

### 入力データソース

- `getStaticProps`の戻り値のrevalidateフィールド
- プリレンダーマニフェスト（prerender-manifest.json）
- リクエストヘッダー（オンデマンド再検証用）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| IncrementalCacheEntry | IncrementalCacheEntry | キャッシュエントリ（HTML/JSON + メタデータ） |
| CacheHandlerValue | CacheHandlerValue | キャッシュ値（lastModified、age、value） |

### 出力先

- メモリキャッシュ（LRUCache）
- ファイルシステムキャッシュ（.next/cache/）
- カスタムCacheHandler（設定時）

## 処理フロー

### 処理シーケンス

```
1. リクエスト受信
   └─ IncrementalCache.get()でキャッシュ確認
2. キャッシュヒット判定
   └─ isStale: revalidate時間を超えているか確認
   └─ タグの有効期限確認（areTagsStale / areTagsExpired）
3. キャッシュヒット（有効）
   └─ キャッシュ済みHTML/JSONをそのまま返却
4. キャッシュヒット（stale）
   └─ キャッシュ済みページを即座に返却
   └─ バックグラウンドでgetStaticPropsを再実行
   └─ 新しいHTMLを生成してキャッシュ更新
5. キャッシュミス
   └─ getStaticPropsを実行してHTMLを生成
   └─ キャッシュに保存して返却
```

### フローチャート

```mermaid
flowchart TD
    A[リクエスト受信] --> B[IncrementalCache.get]
    B --> C{キャッシュヒット?}
    C -->|No| D[getStaticProps実行]
    C -->|Yes| E{stale?}
    E -->|No| F[キャッシュ返却]
    E -->|Yes| G[キャッシュ返却 + バックグラウンド再生成]
    D --> H[HTML生成]
    H --> I[キャッシュ保存]
    I --> J[レスポンス返却]
    G --> K[バックグラウンドgetStaticProps]
    K --> L[キャッシュ更新]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-36-01 | stale-while-revalidate | revalidate時間を超えたキャッシュはstaleとなるが、即座に返却しつつバックグラウンドで再生成する | revalidateが正の整数の場合 |
| BR-36-02 | ロック機構 | 同一キャッシュキーの同時再生成を防ぐためのロック機構が存在する | キャッシュ更新時 |
| BR-36-03 | 開発モード動作 | 開発モードではrevalidateは常に現在時刻 - 1秒に設定される（常にstale） | dev === true |
| BR-36-04 | オンデマンド再検証 | PRERENDER_REVALIDATE_HEADERがpreviewModeIdと一致する場合にオンデマンド再検証が有効化される | 再検証リクエスト |
| BR-36-05 | タグベース無効化 | cacheTagで設定されたタグを使用して、関連するキャッシュエントリを一括無効化できる | revalidateTag使用時 |

### 計算ロジック

revalidateAfterの計算:
```
revalidateAfter = typeof revalidateSeconds === 'number'
  ? revalidateSeconds * 1000 + fromTime
  : revalidateSeconds  // false の場合
```

## データベース操作仕様

該当なし。ISR自体はデータベース操作を行わないが、getStaticProps内で開発者がデータベースアクセスを実装する。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | Error | キャッシュファイルの読み込み失敗 | リトライ機構で再試行 |
| - | Error | バックグラウンド再生成時のgetStaticPropsエラー | 既存キャッシュを維持 |
| - | InvariantError | 無効な状態遷移 | 内部エラーとして処理 |

### リトライ仕様

マニフェストファイルの読み込みは最大3回リトライする（開発モード時）。リトライ間隔は100ms。

## トランザクション仕様

キャッシュ更新はロック機構（`IncrementalCache.lock()`）により排他制御される。

## パフォーマンス要件

- キャッシュヒット時はファイルシステムまたはメモリからの読み出しのみで高速応答
- stale応答とバックグラウンド再生成の分離によりユーザー体験に影響しない
- `NEXT_PRIVATE_DEBUG_CACHE`環境変数でデバッグログを有効化可能

## セキュリティ考慮事項

- オンデマンド再検証はpreviewModeIdによる認証が必要
- キャッシュされたデータは.next/cacheに平文で保存されるため、機密情報の取り扱いに注意
- NEXT_CACHE_TAGS_HEADERによるタグ情報は内部ヘッダーとして扱われる

## 備考

- `NEXT_PRIVATE_DEBUG_CACHE`環境変数でISRのデバッグログを有効化可能
- カスタムCacheHandlerを実装することでRedis等の外部キャッシュバックエンドを利用可能
- App Routerではfetch APIの`next.revalidate`オプションでISRを制御する

---

## コードリーディングガイド

本機能を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | index.ts | `packages/next/src/server/lib/incremental-cache/index.ts` | IncrementalCacheクラス、CacheHandler、CacheHandlerContextの型定義を確認する |

**読解のコツ**: **40-57行目**に`CacheHandlerContext`と`CacheHandlerValue`が定義されている。**59-82行目**の`CacheHandler`はカスタムキャッシュバックエンドの基底クラスであり、get/set/revalidateTagメソッドを持つ。

#### Step 2: IncrementalCacheの初期化を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | index.ts | `packages/next/src/server/lib/incremental-cache/index.ts` | constructorの実装を確認する |

**主要処理フロー**:
1. **107-131行目**: コンストラクタの引数（fs、dev、minimalMode等）
2. **141-157行目**: CacheHandlerの選択ロジック（カスタム > グローバル > FileSystemCache）
3. **178行目**: SharedCacheControlsの初期化
4. **182-187行目**: オンデマンド再検証の判定

#### Step 3: キャッシュ計算ロジックを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | index.ts | `packages/next/src/server/lib/incremental-cache/index.ts` | calculateRevalidateメソッドを確認する |

**主要処理フロー**:
- **210-237行目**: `calculateRevalidate` - 再検証時間の計算。開発モードでは常にstale。

#### Step 4: ロック機構を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | index.ts | `packages/next/src/server/lib/incremental-cache/index.ts` | lockメソッドを確認する |

**主要処理フロー**:
- **247-283行目**: `lock` - DetachedPromiseベースのキュー型ロック機構

### プログラム呼び出し階層図

```
リクエスト処理
    │
    ├─ ResponseCache.get()
    │      └─ IncrementalCache.get()
    │             └─ CacheHandler.get()
    │                    └─ FileSystemCache.get() / カスタムCacheHandler.get()
    │
    ├─ [stale判定]
    │      └─ calculateRevalidate()
    │      └─ areTagsStale() / areTagsExpired()
    │
    └─ [バックグラウンド再生成]
           ├─ IncrementalCache.lock()
           ├─ getStaticProps() → HTML生成
           └─ IncrementalCache.set()
                  └─ CacheHandler.set()
```

### データフロー図

```
[入力]                      [処理]                          [出力]

リクエスト ──────▶ ResponseCache ──────▶ キャッシュ確認
                        │                      │
               IncrementalCache.get() ──▶ CacheHandler.get()
                        │                      │
               [stale判定] ──────────▶ revalidateAfter計算
                        │                      │
               [再生成] ──────────────▶ getStaticProps()
                        │                      │
               IncrementalCache.set() ──▶ キャッシュ更新
                        │
               レスポンス返却 ──────────▶ HTMLレスポンス
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| index.ts | `packages/next/src/server/lib/incremental-cache/index.ts` | ソース | IncrementalCacheの中核実装 |
| file-system-cache.ts | `packages/next/src/server/lib/incremental-cache/file-system-cache.ts` | ソース | ファイルシステムベースのキャッシュ実装 |
| memory-cache.external.ts | `packages/next/src/server/lib/incremental-cache/memory-cache.external.ts` | ソース | メモリキャッシュ実装 |
| shared-cache-controls.external.ts | `packages/next/src/server/lib/incremental-cache/shared-cache-controls.external.ts` | ソース | 共有キャッシュ制御 |
| tags-manifest.external.ts | `packages/next/src/server/lib/incremental-cache/tags-manifest.external.ts` | ソース | タグマニフェスト管理 |
| index.ts | `packages/next/src/server/response-cache/index.ts` | ソース | レスポンスキャッシュ |
