# 通知設計書 11-ページリロード通知

## 概要

本ドキュメントは、Next.js開発サーバーにおける「ページリロード通知」（HMR_MESSAGE_SENT_TO_BROWSER.RELOAD_PAGE）の設計を記述する。この通知は、_document.jsの変更検出時やHMRセッションエラー発生時に、ブラウザへWebSocket経由でページリロード要求を送信する仕組みである。

### 本通知の処理概要

この通知は、開発サーバーからブラウザに対して「現在表示中のページを完全にリロードする必要がある」ことを伝達するHMRメッセージである。

**業務上の目的・背景**：Next.jsの開発環境において、`_document.js`の変更やHMRサブスクリプションエラーなど、Hot Module Replacement（部分更新）では対応できない変更が発生した場合に、ブラウザ側でページのフルリロードを強制する必要がある。これにより、開発者は手動でリロードすることなく、常に最新のコードが反映された状態でページを確認できる。

**通知の送信タイミング**：以下のイベント発生時に送信される。(1) `_document.js`（`_document.tsx`）ファイルに変更が検出された場合。(2) HMRイベントサブスクリプションでエラーが発生した場合（例：前回のサーバーセッションからの古いHMRセッション使用時）。(3) ページデータサブスクリプションでエラーが発生した場合。(4) ページHTMLサブスクリプションでエラーが発生した場合。

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

**通知内容の概要**：メッセージタイプ`reloadPage`と、リロード理由を示すdata文字列が含まれる。

**期待されるアクション**：ブラウザはメッセージ受信後、`window.location.reload()`を呼び出してページの完全リロードを実行する。

## 通知種別

WebSocket（アプリ内通知） - ブラウザへのHMRメッセージ

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | 非同期（WebSocket push） |
| 優先度 | 高 |
| リトライ | 無（WebSocket接続中のクライアントに即時送信） |

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

WebSocket接続を確立しているすべてのブラウザクライアントに対して送信される。Turbopackモードでは`sendToClient`関数を通じて個別クライアントに送信されるケース（HMRサブスクリプションエラー時）と、`sendHmr`関数を通じて全クライアントに送信されるケース（`_document`変更時）がある。Webpackモードでは`WebpackHotMiddleware.publish`を通じて全クライアントに送信される。

## 通知テンプレート

### メール通知の場合

該当なし（WebSocketメッセージであり、メール通知ではない）

### 本文テンプレート

```json
{
  "type": "reloadPage",
  "data": "{リロード理由を示す文字列}"
}
```

dataフィールドの値の例：
- `"_document has changed (page route)"` - _documentファイルの変更時
- `"error in HMR event subscription for {id}: {error}"` - HMRサブスクリプションエラー時
- `"error in {page} data subscription: {error}"` - ページデータサブスクリプションエラー時
- `"error in {page} html subscription: {error}"` - ページHTMLサブスクリプションエラー時
- `"error in {page} app-page subscription: {error}"` - App Pageサブスクリプションエラー時

### 添付ファイル

該当なし

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| type | メッセージタイプ識別子 | HMR_MESSAGE_SENT_TO_BROWSER.RELOAD_PAGE定数 (`reloadPage`) | Yes |
| data | リロード理由を示す文字列 | 変更検出ロジックまたはエラーメッセージから動的生成 | Yes |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| ファイル変更 | `_document.js`/`_document.tsx`の変更検出 | Turbopackのchangeサブスクリプションで変更が通知された場合 | `turbopack-utils.ts`のhandleRouteType内でサブスクリプション登録 |
| エラー | HMRイベントサブスクリプションのエラー | WebSocket経由のHMRサブスクリプションでエラーが発生した場合 | `hot-reloader-turbopack.ts`のsubscribeToHmrEvents内 |
| エラー | ページデータサブスクリプションのエラー | ページのdataEndpointのchangeサブスクリプションでエラーが発生した場合 | `turbopack-utils.ts`のhandleRouteType内 |
| エラー | ページHTMLサブスクリプションのエラー | ページのhtmlEndpointのchangeサブスクリプションでエラーが発生した場合 | `turbopack-utils.ts`のhandleRouteType内 |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| WebSocket未接続 | ブラウザがWebSocket接続を確立していない場合は送信されない |
| コンパイルエラー中 | Turbopackモードでは、コンパイルエラーが存在する間はHMRメッセージがキューに保持され、エラー解消後に送信される（sendEnqueuedMessages） |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[ファイル変更検出 / サブスクリプションエラー] --> B{トリガー種別判定}
    B -->|_document変更| C[ReloadPageMessage生成]
    B -->|サブスクリプションエラー| D[エラー情報付きReloadPageMessage生成]
    C --> E[sendHmr経由で全クライアントのキューに追加]
    D --> F{エラー発生箇所}
    F -->|HMRイベントサブスクリプション| G[sendToClientで個別クライアントに直接送信]
    F -->|changeサブスクリプション| E
    E --> H[sendEnqueuedMessagesでJSON.stringifyして送信]
    G --> I[クライアント側でwindow.location.reload実行]
    H --> I
```

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

### 参照テーブル一覧

該当なし（データベースを使用しない。ファイルシステムの変更検出に基づく通知）

### 更新テーブル一覧

該当なし

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| WebSocket送信失敗 | クライアントのWebSocket接続が切断されている場合 | 送信はスキップされる。クライアント側ではcloseイベントでクリーンアップが実行される |
| クライアント側リロード失敗 | ネットワーク障害等でリロードが完了しない場合 | ブラウザ側の処理であり、サーバーからは制御不可 |

### リトライ仕様

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

## 配信設定

### レート制限

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

### 配信時間帯

開発サーバー稼働中は常時送信可能（開発環境専用の通知）

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

- 本通知は開発環境（`next dev`）でのみ使用され、本番環境には含まれない
- WebSocket接続はローカル開発サーバーに対して行われるため、外部からの不正アクセスリスクは限定的
- リロード理由のdata文字列にはエラーメッセージが含まれる場合があるが、開発環境のためセキュリティ上の問題はない

## 備考

- Turbopackモードでは、HMRサブスクリプションエラー時にReloadPageメッセージ送信後、WebSocket接続自体をclose()する（`hot-reloader-turbopack.ts` 626行目）
- Pages Routerでは`page-bootstrap.ts`にてRELOAD_PAGEメッセージを受信し、`window.location.reload()`を実行する
- App Routerでは`hot-reloader-app.tsx`のprocessMessage内でRELOAD_PAGEを処理し、同様にリロードを実行する
- `reloading`フラグにより、複数のリロード要求が同時に発生しても1回のみリロードが実行される

---

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

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

### 推奨読解順序

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

まず、ReloadPageMessageの型定義を理解することが重要。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | hot-reloader-types.ts | `packages/next/src/server/dev/hot-reloader-types.ts` | 105-108行目: ReloadPageMessage型の定義。typeフィールドとdataフィールドの構造を確認 |
| 1-2 | hot-reloader-types.ts | `packages/next/src/server/dev/hot-reloader-types.ts` | 18-22行目: HMR_MESSAGE_SENT_TO_BROWSER enumのRELOAD_PAGE定義（値は`reloadPage`） |

**読解のコツ**: TypeScriptのconst enumとして定義されているため、コンパイル後は文字列リテラルに置換される。

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

処理の起点となるファイル・関数を特定する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | turbopack-utils.ts | `packages/next/src/server/dev/turbopack-utils.ts` | 300-316行目: `_document`変更時のサブスクリプション登録。変更検出時にRELOAD_PAGEメッセージを生成 |
| 2-2 | turbopack-utils.ts | `packages/next/src/server/dev/turbopack-utils.ts` | 262-298行目: ページのdataEndpoint/htmlEndpointのエラー時にRELOAD_PAGEメッセージを生成 |
| 2-3 | hot-reloader-turbopack.ts | `packages/next/src/server/dev/hot-reloader-turbopack.ts` | 620-628行目: HMRサブスクリプションエラー時にRELOAD_PAGEメッセージを送信しクライアント接続をclose |

**主要処理フロー**:
1. **300-308行目**: `_document`のchangeサブスクリプションが変更を検出し、RELOAD_PAGEメッセージを生成
2. **509-519行目**: sendHmr関数で全クライアントのメッセージキューにメッセージを追加
3. **463-506行目**: sendEnqueuedMessages関数でキュー内のメッセージをJSON.stringifyしてWebSocket送信

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | hot-reloader-app.tsx | `packages/next/src/client/dev/hot-reloader/app/hot-reloader-app.tsx` | 436-447行目: App RouterでのRELOAD_PAGE処理。sendMessageでサーバーにイベント通知し、window.location.reload()を実行 |
| 3-2 | page-bootstrap.ts | `packages/next/src/client/page-bootstrap.ts` | 37-40行目: Pages RouterでのRELOAD_PAGE処理。reloadingフラグを設定しwindow.location.reload()を実行 |
| 3-3 | turbopack-hot-reloader-common.ts | `packages/next/src/client/dev/hot-reloader/turbopack-hot-reloader-common.ts` | 87-89行目: TurbopackHmrクラスのonReloadPage()メソッド。HMRラテンシ計測用の更新処理 |

**主要処理フロー**:
- **436行目**: App Routerがメッセージ受信
- **437行目**: TurbopackHmrのonReloadPage()を呼び出し
- **444-446行目**: reloadingフラグチェック後、window.location.reload()を実行

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

```
[サーバー側]
turbopack-utils.ts: handleRouteType()
    |
    +-- subscribeToChanges() (_documentのchangeサブスクリプション)
    |       |
    |       +-- createMessage() -> { type: RELOAD_PAGE, data: "..." }
    |               |
    |               +-- sendHmr() (全クライアントのキューに追加)
    |                       |
    |                       +-- sendEnqueuedMessagesDebounce()
    |                               |
    |                               +-- sendEnqueuedMessages()
    |                                       |
    |                                       +-- sendToClient() -> client.send(JSON.stringify(message))
    |
hot-reloader-turbopack.ts: subscribeToHmrEvents()
    |
    +-- [エラー発生時]
            |
            +-- sendToClient() -> client.send(JSON.stringify(reloadMessage))
            +-- client.close()

[クライアント側]
hot-reloader-app.tsx: processMessage()
    |
    +-- case RELOAD_PAGE:
            +-- turbopackHmr.onReloadPage()
            +-- sendMessage({ event: 'client-reload-page' })
            +-- window.location.reload()

page-bootstrap.ts: addMessageListener()
    |
    +-- case RELOAD_PAGE:
            +-- reloading = true
            +-- window.location.reload()
```

### データフロー図

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

ファイル変更検出           サーバー側                         ブラウザ
(_document.js変更)  -----> Turbopack changeサブスクリプション
                           |
                           v
                    ReloadPageMessage生成
                    { type: "reloadPage",
                      data: "..." }
                           |
                           v
                    sendHmr() / sendToClient()
                           |
                           v
                    WebSocket JSON送信  ------------>  processMessage()
                                                       |
                                                       v
                                                window.location.reload()
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| hot-reloader-types.ts | `packages/next/src/server/dev/hot-reloader-types.ts` | ソース | ReloadPageMessage型定義およびHMR_MESSAGE_SENT_TO_BROWSER enum定義 |
| hot-reloader-turbopack.ts | `packages/next/src/server/dev/hot-reloader-turbopack.ts` | ソース | TurbopackモードのHMRサーバー実装。WebSocket管理、メッセージ送信 |
| hot-middleware.ts | `packages/next/src/server/dev/hot-middleware.ts` | ソース | WebpackモードのHMRミドルウェア実装 |
| turbopack-utils.ts | `packages/next/src/server/dev/turbopack-utils.ts` | ソース | Turbopackのルート処理ユーティリティ。changeサブスクリプションでRELOAD_PAGEを生成 |
| hot-reloader-app.tsx | `packages/next/src/client/dev/hot-reloader/app/hot-reloader-app.tsx` | ソース | App Router用クライアント側HMRメッセージ処理 |
| hot-reloader-pages.ts | `packages/next/src/client/dev/hot-reloader/pages/hot-reloader-pages.ts` | ソース | Pages Router用クライアント側HMRメッセージ処理 |
| page-bootstrap.ts | `packages/next/src/client/page-bootstrap.ts` | ソース | Pages Routerのブートストラップ処理。RELOAD_PAGEメッセージの受信と実行 |
| turbopack-hot-reloader-common.ts | `packages/next/src/client/dev/hot-reloader/turbopack-hot-reloader-common.ts` | ソース | Turbopack共通のHMRラテンシ計測ロジック |
