# 通知設計書 12-サーバーコンポーネント変更通知

## 概要

本ドキュメントは、Next.js開発サーバーにおける「サーバーコンポーネント変更通知」（HMR_MESSAGE_SENT_TO_BROWSER.SERVER_COMPONENT_CHANGES）の設計を記述する。この通知は、Server Componentの変更を検出した際にブラウザへWebSocket経由で通知し、App Routerのリフレッシュまたはページリロードをトリガーする。

### 本通知の処理概要

この通知は、React Server Component（RSC）のソースコードに変更が検出された際に、開発サーバーからブラウザへHMRメッセージとして送信される仕組みである。

**業務上の目的・背景**：Next.jsのApp Routerでは、Server Componentはサーバーサイドでレンダリングされるため、クライアント側のHot Module Replacement（HMR）だけでは変更を反映できない。この通知により、Server Componentの変更時にApp Routerの`hmrRefresh()`を呼び出し、サーバーから最新のRSCペイロードを再取得することで、開発者がページを手動でリロードすることなくServer Componentの変更を即座に確認できる。

**通知の送信タイミング**：以下のイベント発生時に送信される。(1) App Pageルートの`rscEndpoint`で変更が検出された場合（エラーのない変更のみ）。(2) `.env`ファイルやtsconfig/jsconfig変更によるinvalidation後のリロード時。

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

**通知内容の概要**：メッセージタイプ`serverComponentChanges`と、コンパイルハッシュ値が含まれる。

**期待されるアクション**：App RouterクライアントはhmrRefresh()を呼び出してサーバーから最新のRSCペイロードを取得し、UIを更新する。ランタイムエラーが発生していた場合はページのフルリロードを実行する。Pages Routerクライアントでは、コンパイルエラーまたはランタイムエラーがあった場合のみページリロードを実行する。

## 通知種別

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

## 送信仕様

### 基本情報

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

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

Turbopackモードでは`sendHmr`関数を通じて全クライアントのメッセージキューに追加される。invalidation時は`hotReloader.send()`により全WebSocketクライアントに直接JSON送信される。コンパイルエラーが存在する間はメッセージがキューに保持され、エラー解消後に送信される。

## 通知テンプレート

### メール通知の場合

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

### 本文テンプレート

```json
{
  "type": "serverComponentChanges",
  "hash": "{コンパイルハッシュ値}"
}
```

### 添付ファイル

該当なし

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| type | メッセージタイプ識別子 | HMR_MESSAGE_SENT_TO_BROWSER.SERVER_COMPONENT_CHANGES定数 (`serverComponentChanges`) | Yes |
| hash | コンパイルハッシュ値（インクリメンタルカウンタの文字列表現） | `hmrHash`カウンタを`String(++hmrHash)`で変換 | Yes |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| ファイル変更 | App PageルートのrscEndpoint変更 | 変更にseverity=errorのissueが含まれないこと | turbopack-utils.ts handleRouteType内のapp-pageケースでサブスクリプション登録 |
| 環境変更 | .envファイルやtsconfig/jsconfig変更によるinvalidation | reloadAfterInvalidationフラグがtrueであること | hot-reloader-turbopack.ts invalidate()メソッド内 |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| コンパイルエラー存在 | rscEndpointの変更結果にseverity=errorのissueが含まれる場合、メッセージは生成されない |
| WebSocket未接続 | ブラウザがWebSocket接続を確立していない場合は送信されない |
| sendEnqueuedMessagesの抑止 | currentEntryIssuesにエラーが存在する間はキュー内メッセージの送信が保留される |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[RSCファイル変更検出] --> B{エラー有無チェック}
    B -->|エラーあり| C[メッセージ生成スキップ]
    B -->|エラーなし| D[ServerComponentChangesMessage生成]
    D --> E[sendHmr経由で全クライアントのキューに追加]
    E --> F[sendEnqueuedMessagesでJSON送信]
    F --> G{クライアント種別}
    G -->|App Router| H[hmrRefresh実行 - RSCペイロード再取得]
    G -->|Pages Router| I{エラー状態チェック}
    I -->|エラーあり| J[window.location.reload]
    I -->|エラーなし| K[何もしない]
    H --> L[UI更新]
```

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

### 参照テーブル一覧

該当なし（データベースを使用しない）

### 更新テーブル一覧

該当なし

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| ランタイムエラー既存 | クライアント側でランタイムエラーが発生していた場合 | App Routerではwindow.location.reload()でフルリロード |
| エラーページ表示中 | document.documentElement.idが`__next_error__`の場合 | App Routerではwindow.location.reload()でフルリロード |
| コンパイルエラー | Pages RouterでhasCompileErrorsがtrueの場合 | window.location.reload()でフルリロード |

### リトライ仕様

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

## 配信設定

### レート制限

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

### 配信時間帯

開発サーバー稼働中は常時送信可能

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

- 本通知は開発環境（`next dev`）でのみ使用される
- ハッシュ値はインクリメンタルカウンタであり、セッションCookieとして設定される（`NEXT_HMR_REFRESH_HASH_COOKIE`）
- WebSocket接続はローカル開発サーバーに対して行われる

## 備考

- App Routerクライアントでは、受信したhash値を`NEXT_HMR_REFRESH_HASH_COOKIE`というセッションCookieに保存し、後続のサーバーリクエストに送信する（hot-reloader-app.tsx 411行目）
- `publicAppRouterInstance.hmrRefresh()`は`startTransition`内で呼び出され、Reactのトランジションとして処理される
- Pages Routerでは、Server Componentの概念がないため、コンパイルエラーまたはランタイムエラーがある場合のみリロードを実行する
- invalidation（.env変更等）時は、サーバー側のキャッシュクリア（clearAllModuleContexts）も同時に実行される

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | hot-reloader-types.ts | `packages/next/src/server/dev/hot-reloader-types.ts` | 110-113行目: ServerComponentChangesMessage型定義。typeフィールドとhashフィールドの構造 |
| 1-2 | hot-reloader-types.ts | `packages/next/src/server/dev/hot-reloader-types.ts` | 23行目: HMR_MESSAGE_SENT_TO_BROWSER.SERVER_COMPONENT_CHANGES = `serverComponentChanges` |

**読解のコツ**: hashフィールドはstring型であり、Turbopackモードではインクリメンタルカウンタの文字列表現が使用される。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | turbopack-utils.ts | `packages/next/src/server/dev/turbopack-utils.ts` | 357-380行目: app-pageルートのrscEndpointに対するchangeサブスクリプション。エラーなしの場合にSERVER_COMPONENT_CHANGESメッセージを生成 |
| 2-2 | hot-reloader-turbopack.ts | `packages/next/src/server/dev/hot-reloader-turbopack.ts` | 1270-1284行目: invalidate()メソッド。reloadAfterInvalidation時にSERVER_COMPONENT_CHANGESを送信 |

**主要処理フロー**:
1. **357-361行目**: rscEndpointのchangeサブスクリプションが変更を検出
2. **362行目**: 変更にseverity=errorのissueがある場合はスキップ
3. **369-372行目**: SERVER_COMPONENT_CHANGESメッセージを生成し、hashを設定
4. **509-519行目**（hot-reloader-turbopack.ts）: sendHmr経由で全クライアントのキューに追加

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | hot-reloader-app.tsx | `packages/next/src/client/dev/hot-reloader/app/hot-reloader-app.tsx` | 399-435行目: App RouterでのSERVER_COMPONENT_CHANGES処理。hash値をCookieに保存し、hmrRefresh()を呼び出す |
| 3-2 | hot-reloader-pages.ts | `packages/next/src/client/dev/hot-reloader/pages/hot-reloader-pages.ts` | 342-348行目: Pages RouterでのSERVER_COMPONENT_CHANGES処理。エラー時のみリロード |

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

```
[サーバー側 - 通常のRSC変更]
turbopack-utils.ts: handleRouteType() [app-pageケース]
    |
    +-- subscribeToChanges(key, true, route.rscEndpoint, ...)
            |
            +-- createMessage(change, hash) -> { type: SERVER_COMPONENT_CHANGES, hash }
                    |
                    +-- sendHmr(key, message)
                            |
                            +-- sendEnqueuedMessagesDebounce()
                                    +-- sendEnqueuedMessages() -> JSON送信

[サーバー側 - invalidation]
hot-reloader-turbopack.ts: invalidate()
    |
    +-- clearRequireCache() (全エントリポイント)
    +-- clearAllModuleContexts()
    +-- hotReloader.send({ type: SERVER_COMPONENT_CHANGES, hash })
            |
            +-- JSON.stringify() -> 全クライアントにWebSocket送信

[クライアント側 - App Router]
hot-reloader-app.tsx: processMessage()
    |
    +-- case SERVER_COMPONENT_CHANGES:
            +-- turbopackHmr.onServerComponentChanges()
            +-- sendMessage({ event: 'server-component-reload-page' })
            +-- document.cookie = NEXT_HMR_REFRESH_HASH_COOKIE=hash
            +-- publicAppRouterInstance.hmrRefresh()
            +-- dispatcher.onRefresh()
```

### データフロー図

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

RSCファイル変更          サーバー側                             ブラウザ(App Router)
                  -----> rscEndpoint changeサブスクリプション
                         |
                         v
                  エラーチェック(severity=error除外)
                         |
                         v
                  ServerComponentChangesMessage生成
                  { type: "serverComponentChanges",
                    hash: "incrementalCounter" }
                         |
                         v
                  sendHmr() -> WebSocket JSON送信 -------> processMessage()
                                                           |
                                                           v
                                                    Cookie設定 (hash保存)
                                                           |
                                                           v
                                                    hmrRefresh() -> RSCペイロード再取得
                                                           |
                                                           v
                                                    UI更新
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| hot-reloader-types.ts | `packages/next/src/server/dev/hot-reloader-types.ts` | ソース | ServerComponentChangesMessage型定義 |
| hot-reloader-turbopack.ts | `packages/next/src/server/dev/hot-reloader-turbopack.ts` | ソース | Turbopackモード送信ロジック、invalidate処理 |
| turbopack-utils.ts | `packages/next/src/server/dev/turbopack-utils.ts` | ソース | rscEndpointのchangeサブスクリプション登録 |
| hot-reloader-app.tsx | `packages/next/src/client/dev/hot-reloader/app/hot-reloader-app.tsx` | ソース | App Routerクライアント側の受信処理 |
| hot-reloader-pages.ts | `packages/next/src/client/dev/hot-reloader/pages/hot-reloader-pages.ts` | ソース | Pages Routerクライアント側の受信処理 |
| turbopack-hot-reloader-common.ts | `packages/next/src/client/dev/hot-reloader/turbopack-hot-reloader-common.ts` | ソース | TurbopackHmr.onServerComponentChanges()メソッド |
| app-router-instance.ts | `packages/next/src/client/components/app-router-instance.ts` | ソース | publicAppRouterInstance.hmrRefresh()の実装 |
