# 通知設計書 23-ISRマニフェスト通知

## 概要

本ドキュメントは、Next.js開発サーバーにおけるISR（Incremental Static Regeneration）マニフェスト通知の設計を記述する。WebSocket接続確立時にISRマニフェスト情報をブラウザクライアントへ送信し、各ルートが静的か動的かをクライアント側で判定可能にする仕組みについて定義する。

### 本通知の処理概要

開発サーバーでレガシークライアント（Pages RouterクライアントまたはCache Components無効のApp Routerクライアント）がWebSocket接続を確立した際に、サーバーが保持するISRマニフェスト（各ルートパスの静的/動的フラグ）をクライアントへ送信する。

**業務上の目的・背景**：開発中にDev Overlayの静的インジケーター（Static Indicator）を表示するために、各ルートがISR対象（静的）か動的かの情報が必要となる。本通知によりクライアントはマニフェスト情報を取得し、現在表示中のページの静的/動的状態をインジケーターに反映できる。

**通知の送信タイミング**：WebSocket接続確立時の`onUpgrade`コールバック内で、レガシークライアントに対してのみ送信される。Cache Components有効のApp Routerクライアントには送信されない（Cache Componentsが有効な場合、部分的に静的なページの表現ができないため）。

**通知の受信者**：レガシーWebSocketクライアント（Pages RouterクライアントまたはCache Components無効のApp Routerクライアント）。

**通知内容の概要**：`type: "isrManifest"`と`data: Record<string, boolean>`を含むJSONメッセージ。キーはルートパス、値は`true`（静的）または`false`（動的）。

**期待されるアクション**：App Routerクライアント（`hot-reloader-app.tsx`）は受信したマニフェストを`staticIndicatorState.appIsrManifest`に格納し、現在のパス名に基づいてDev Overlayの静的インジケーター状態を`static`/`dynamic`/`pending`に更新する。

## 通知種別

WebSocket（HMR）メッセージ（アプリ内通知）

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | 同期（WebSocket接続確立時に送信） |
| 優先度 | 中 |
| リトライ | なし |

### 送信先決定ロジック

`router-server.ts`のWebSocket `onUpgrade`コールバック内で、`isLegacyClient`が`true`の場合にのみ`client.send()`でISRマニフェストを送信する。`isLegacyClient`はCache Components無効のApp Routerクライアントまたは Pages Routerクライアント。

## 通知テンプレート

### メール通知の場合

該当なし（WebSocketメッセージのため）

### 本文テンプレート

```json
{
  "type": "isrManifest",
  "data": {
    "/": true,
    "/about": true,
    "/dashboard": false,
    "/api/data": false
  }
}
```

### 添付ファイル

該当なし

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| type | メッセージ種別 | 固定値 `"isrManifest"` | Yes |
| data | ルートパスと静的フラグのマッピング | `development.service?.appIsrManifest \|\| {}` | Yes |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| サーバーイベント | WebSocket接続確立（onUpgradeコールバック） | `isLegacyClient === true` | レガシークライアント接続時のみ |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| Cache Components有効のApp Routerクライアント | `isLegacyClient`が`false`の場合は送信されない |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[ブラウザからWebSocket接続要求] --> B[onHMR → handleUpgrade]
    B --> C{isLegacyClient?}
    C -->|Yes| D[ISRマニフェスト送信]
    C -->|No| E[送信スキップ]
    D --> F[クライアント: appIsrManifest格納]
    F --> G[静的インジケーター状態更新]
    E --> H[終了]
    G --> H
```

## データベース参照・更新仕様

### 参照テーブル一覧

該当なし（`development.service?.appIsrManifest`からインメモリのマニフェストデータを参照）

### 更新テーブル一覧

該当なし

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| マニフェスト未生成 | `development.service`が未初期化 | 空オブジェクト `{}` をフォールバックとして送信 |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | なし |
| リトライ間隔 | 該当なし |
| リトライ対象エラー | 該当なし |

## 配信設定

### レート制限

| 項目 | 内容 |
|-----|------|
| 1分あたり上限 | 制限なし |
| 1日あたり上限 | 制限なし |

### 配信時間帯

制限なし（開発サーバー稼働中は常時送信可能）

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

本通知はローカル開発環境専用。ルートパスの一覧が含まれるが、開発環境のため情報漏洩リスクは低い。

## 備考

- Cache Components有効時はISRマニフェストが送信されない理由は、部分的に静的なページを二値（static/dynamic）で表現できないため。
- マニフェストの更新はエントリーポイント変更時に行われるが、ISRマニフェスト通知自体は接続時に1回のみ送信される。マニフェスト更新後の差分通知は現時点では実装されていない。

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | hot-reloader-types.ts | `packages/next/src/server/dev/hot-reloader-types.ts` | 34行目: `ISR_MANIFEST = 'isrManifest'`のenum定義。142-145行目: `AppIsrManifestMessage`インターフェースで`data: Record<string, boolean>`の構造を確認 |

#### Step 2: サーバー側送信ロジックを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | router-server.ts | `packages/next/src/server/lib/router-server.ts` | 852-869行目: `onUpgrade`コールバック内で`isLegacyClient`の場合のみISRマニフェストを`client.send()`で送信 |

**主要処理フロー**:
1. **848行目**: `development.bundler.hotReloader.onHMR`を呼び出し
2. **852行目**: `onUpgrade`コールバック開始、`isLegacyClient`フラグを受け取る
3. **853行目**: `isLegacyClient`の条件判定
4. **861-866行目**: `ISR_MANIFEST`メッセージを`client.send()`で送信。データは`development.service?.appIsrManifest || {}`

#### Step 3: クライアント側受信処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | hot-reloader-app.tsx | `packages/next/src/client/dev/hot-reloader/app/hot-reloader-app.tsx` | 267-284行目: `ISR_MANIFEST`受信時の処理。`staticIndicatorState.appIsrManifest`に格納し、`dispatcher.onStaticIndicator()`で静的インジケーター状態を更新 |

**主要処理フロー**:
- **268行目**: `process.env.__NEXT_DEV_INDICATOR`の条件チェック
- **269行目**: `staticIndicatorState.appIsrManifest = message.data`で格納
- **276-281行目**: 現在のパス名に対するISR状態を判定し、`dispatcher.onStaticIndicator()`を呼び出し

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

```
router-server.ts: upgradeHandler [router-server.ts:840-870]
    |
    +-- development.bundler.hotReloader.onHMR(req, socket, head, onUpgrade)
           |
           +-- onUpgrade(client, { isLegacyClient }) [router-server.ts:852]
                  |
                  +-- [isLegacyClient=true] client.send(JSON.stringify({
                  |     type: ISR_MANIFEST,
                  |     data: appIsrManifest
                  |   }))
                  |
                  +-- [Browser] processMessage(ISR_MANIFEST) [hot-reloader-app.tsx:267]
                         |
                         +-- dispatcher.onStaticIndicator() [hot-reloader-app.tsx:280]
```

### データフロー図

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

development.service         onUpgrade コールバック            WebSocket JSON メッセージ
  .appIsrManifest    ──>      isLegacyClient判定       ──>   type: "isrManifest"
  (Record<string,boolean>)    マニフェスト取得                 data: {"/": true, ...}
                                                                |
                                                                v
                                                          [ブラウザクライアント]
                                                          staticIndicatorState更新
                                                          dispatcher.onStaticIndicator()
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| hot-reloader-types.ts | `packages/next/src/server/dev/hot-reloader-types.ts` | ソース | メッセージ型定義・enum定義 |
| router-server.ts | `packages/next/src/server/lib/router-server.ts` | ソース | サーバー側送信ロジック（onUpgradeコールバック） |
| hot-reloader-app.tsx | `packages/next/src/client/dev/hot-reloader/app/hot-reloader-app.tsx` | ソース | App Routerクライアント側受信処理 |
| dev-bundler-service.ts | `packages/next/src/server/lib/dev-bundler-service.ts` | ソース | appIsrManifestの管理 |
| page-bootstrap.ts | `packages/next/src/client/page-bootstrap.ts` | ソース | Pages Routerクライアント（no-op） |
