# 通知設計書 21-DevPagesManifest更新通知

## 概要

本ドキュメントは、Next.js開発サーバーにおけるDevPagesManifest更新通知の設計を記述する。開発用ページマニフェストが更新された際にブラウザへWebSocket経由で通知し、クライアント側でマニフェストの再取得を促す仕組みについて定義する。

### 本通知の処理概要

開発サーバーでルート（ページ）の追加・削除が検出された際に、ブラウザクライアントへWebSocket経由で`devPagesManifestUpdate`メッセージを送信し、クライアント側で開発用ページマニフェストJSONの再取得を行わせる。

**業務上の目的・背景**：開発中にページファイルを追加・削除した場合、ブラウザ側が保持するページ一覧（マニフェスト）が古い状態のままとなる。本通知により、ページ構成の変更をリアルタイムにブラウザへ反映し、オンデマンドエントリーやルーティングの整合性を維持する。

**通知の送信タイミング**：Turbopackのエントリーポイント購読ループ（`handleEntrypointsSubscription`）内でルートの追加（`addedRoutes`）または削除（`removedRoutes`）が検出されたときに送信される。Webpackモードでは`on-demand-entry-handler`のページ追加・削除時に同等のメッセージが送信される。

**通知の受信者**：WebSocket接続中のすべてのブラウザクライアント（Pages RouterクライアントおよびApp Routerクライアント）。

**通知内容の概要**：`type: "devPagesManifestUpdate"`と`data: [{ devPagesManifest: true }]`を含むJSONメッセージ。マニフェスト本体は含まず、クライアントに再取得を指示するシグナルのみ。

**期待されるアクション**：Pages Routerクライアント（`page-bootstrap.ts`）は`/_next/static/development/_devPagesManifest.json`をfetchし、`window.__DEV_PAGES_MANIFEST`を更新する。App Routerクライアントではこのメッセージはno-opとして処理される。

## 通知種別

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

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | 非同期（WebSocket push） |
| 優先度 | 中 |
| リトライ | なし（WebSocket切断時は再接続後にSYNCメッセージで状態が同期される） |

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

`hotReloader.send()`を使用し、`clientsWithoutHtmlRequestId`および`clientsByHtmlRequestId`に登録されたすべてのWebSocketクライアントへブロードキャストする。

## 通知テンプレート

### メール通知の場合

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

### 本文テンプレート

```json
{
  "type": "devPagesManifestUpdate",
  "data": [
    {
      "devPagesManifest": true
    }
  ]
}
```

### 添付ファイル

該当なし

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| type | メッセージ種別 | 固定値 `"devPagesManifestUpdate"` | Yes |
| data[0].devPagesManifest | マニフェスト更新フラグ | 固定値 `true` | Yes |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| サーバーイベント | エントリーポイント購読でルート追加・削除を検出 | `addedRoutes.length > 0 \|\| removedRoutes.length > 0` | Turbopackモードでのルート変更検出時 |
| サーバーイベント | Webpackコンパイル完了時のページ追加・削除 | ページ一覧の差分あり | Webpackモードでのルート変更検出時 |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| ルート変更なし | `addedRoutes`と`removedRoutes`がともに空の場合は送信されない |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[エントリーポイント購読ループ] --> B[ルート追加・削除の検出]
    B -->|変更あり| C[hotReloader.send でdevPagesManifestUpdateを送信]
    B -->|変更なし| D[送信スキップ]
    C --> E[全WebSocketクライアントへJSON送信]
    E --> F[クライアント: _devPagesManifest.jsonをfetch]
    F --> G[window.__DEV_PAGES_MANIFESTを更新]
    D --> H[終了]
    G --> H
```

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

### 参照テーブル一覧

該当なし（データベースは使用しない。Turbopackのエントリーポイント購読結果を参照）

### 更新テーブル一覧

該当なし

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| マニフェストfetch失敗 | クライアント側で`_devPagesManifest.json`の取得に失敗 | `console.log`でエラー出力し、次回のマニフェスト更新通知を待つ |
| WebSocket切断 | クライアントのWebSocket接続が切れている | 再接続後にSYNCメッセージで状態が同期される |

### リトライ仕様

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

## 配信設定

### レート制限

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

### 配信時間帯

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

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

本通知はローカル開発環境専用であり、本番環境では送信されない。WebSocket接続はローカルホストに限定されるため、外部への情報漏洩リスクは低い。マニフェスト本体ではなくシグナルのみを送信するため、機密情報は含まれない。

## 備考

- Pages Routerクライアントでのみマニフェスト再取得が実行される。App Routerクライアントでは本メッセージはno-opとして処理される（`hot-reloader-app.tsx`の`processMessage`関数内で即`return`）。
- Webpackモードでも同等のメッセージが`on-demand-entry-handler`から送信される。

---

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

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

### 推奨読解順序

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

まず、メッセージの型定義とenum値を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | hot-reloader-types.ts | `packages/next/src/server/dev/hot-reloader-types.ts` | 30行目: `DEV_PAGES_MANIFEST_UPDATE = 'devPagesManifestUpdate'`のenum定義。128-135行目: `DevPagesManifestUpdateMessage`インターフェースでメッセージの型構造を確認 |

**読解のコツ**: `HMR_MESSAGE_SENT_TO_BROWSER`はconst enumとして定義されており、コンパイル時にインライン展開される。

#### Step 2: エントリーポイントを理解する（サーバー側送信）

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | hot-reloader-turbopack.ts | `packages/next/src/server/dev/hot-reloader-turbopack.ts` | 644-747行目: `handleEntrypointsSubscription`関数でルート追加・削除を検出し、718-728行目でDEV_PAGES_MANIFEST_UPDATEメッセージを送信 |

**主要処理フロー**:
1. **644行目**: `handleEntrypointsSubscription`関数でエントリーポイントの変更を購読
2. **672-677行目**: `addedRoutes`と`removedRoutes`を算出
3. **718行目**: ルート変更がある場合に条件判定
4. **720-728行目**: `hotReloader.send()`で`DEV_PAGES_MANIFEST_UPDATE`メッセージを送信

#### Step 3: ブロードキャスト処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | hot-reloader-turbopack.ts | `packages/next/src/server/dev/hot-reloader-turbopack.ts` | 1125-1134行目: `send`メソッドで全クライアントへJSON文字列を送信 |

**主要処理フロー**:
- **1125行目**: `send(action)`メソッドで`JSON.stringify`した後、全クライアントへ送信

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | page-bootstrap.ts | `packages/next/src/client/page-bootstrap.ts` | 42-54行目: Pages Routerクライアントで`devPagesManifestUpdate`を受信し、`_devPagesManifest.json`をfetchして`window.__DEV_PAGES_MANIFEST`を更新 |
| 4-2 | hot-reloader-app.tsx | `packages/next/src/client/dev/hot-reloader/app/hot-reloader-app.tsx` | 464-466行目: App Routerクライアントではno-opとして即return |

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

```
handleEntrypointsSubscription() [hot-reloader-turbopack.ts:644]
    |
    +-- addedRoutes / removedRoutes 算出 [hot-reloader-turbopack.ts:672-677]
    |
    +-- hotReloader.send({ type: DEV_PAGES_MANIFEST_UPDATE }) [hot-reloader-turbopack.ts:720]
           |
           +-- send(action) [hot-reloader-turbopack.ts:1125]
                  |
                  +-- client.send(JSON.stringify(action)) [全WebSocketクライアント]
                         |
                         +-- [Pages Router] page-bootstrap.ts: fetch _devPagesManifest.json
                         +-- [App Router] hot-reloader-app.tsx: no-op
```

### データフロー図

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

Turbopackエントリーポイント ──> handleEntrypointsSubscription() ──> WebSocket JSON メッセージ
  変更検出結果                    ルート差分算出                       type: "devPagesManifestUpdate"
                                                                      |
                                                                      v
                                                                 [ブラウザクライアント]
                                                                 fetch _devPagesManifest.json
                                                                      |
                                                                      v
                                                                 window.__DEV_PAGES_MANIFEST 更新
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| hot-reloader-types.ts | `packages/next/src/server/dev/hot-reloader-types.ts` | ソース | メッセージ型定義・enum定義 |
| hot-reloader-turbopack.ts | `packages/next/src/server/dev/hot-reloader-turbopack.ts` | ソース | Turbopackモードでの送信ロジック |
| page-bootstrap.ts | `packages/next/src/client/page-bootstrap.ts` | ソース | Pages Routerクライアント側受信処理 |
| hot-reloader-app.tsx | `packages/next/src/client/dev/hot-reloader/app/hot-reloader-app.tsx` | ソース | App Routerクライアント側受信処理（no-op） |
| setup-dev-bundler.ts | `packages/next/src/server/lib/router-utils/setup-dev-bundler.ts` | ソース | Webpackモードでの同等処理 |
