# 通知設計書 20-ブラウザ表示エラー通知

## 概要

本ドキュメントは、Next.js開発サーバーにおける「ブラウザ表示エラー通知」（HMR_MESSAGE_SENT_TO_BROWSER.ERRORS_TO_SHOW_IN_BROWSER）の設計を記述する。この通知は、サーバーサイドでシリアライズされたエラー情報をRSCストリームとしてブラウザに送信し、クライアント側でデシリアライズしてコンソールに表示する。

### 本通知の処理概要

この通知は、サーバーサイドで発生したエラーをReact Server Components（RSC）のシリアライゼーション形式でブラウザに送信するバイナリメッセージである。従来のJSONベースのエラー通知とは異なり、RSCプロトコルを使用してErrorオブジェクトをシリアライズ/デシリアライズする。

**業務上の目的・背景**：Next.jsの開発環境において、サーバーサイドで発生した構造化されたエラー情報をリッチにブラウザに伝達する必要がある。RSCのシリアライゼーション形式を使用することで、Errorオブジェクトのプロパティ（message, stack, cause等）を完全に保持してブラウザに送信でき、開発者はサーバーで発生したエラーの詳細をブラウザコンソールで確認できる。

**通知の送信タイミング**：サーバーサイドでエラーのRSCストリームが用意された後、WebSocket接続確立時（HTML requestID付き）に送信される。

**通知の受信者**：WebSocket接続を確立しているApp Routerブラウザクライアント。

**通知内容の概要**：メッセージタイプ（バイナリ）とシリアライズされたエラーのUint8Arrayバイナリデータが含まれる。

**期待されるアクション**：ブラウザはRSCプロトコルでデシリアライズし、エラー配列をコンソールにconsole.errorで表示する。

## 通知種別

WebSocket（アプリ内通知） - バイナリHMRメッセージ

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | 非同期（WebSocket push、バイナリ形式） |
| 優先度 | 高 |
| リトライ | 無 |

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

HTML requestIDに紐づく特定のWebSocketクライアントに対して送信される。`serialized-errors.ts`の`sendSerializedErrorsToClientForHtmlRequest`関数で、htmlRequestIdに対応するエラーRSCストリームが存在する場合に送信される。

## 通知テンプレート

### メール通知の場合

該当なし

### 本文テンプレート

バイナリ形式のメッセージ。最初の1バイトがメッセージタイプ（数値）を表し、残りのバイトがシリアライズされたエラーデータ（Uint8Array）。

```
[1バイト: メッセージタイプ] [Nバイト: シリアライズされたエラーデータ]
```

メッセージタイプは数値でエンコードされる（`HMR_MESSAGE_SENT_TO_BROWSER.ERRORS_TO_SHOW_IN_BROWSER`は数値型のenum値）。

### 添付ファイル

該当なし

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| type | メッセージタイプ識別子（数値） | HMR_MESSAGE_SENT_TO_BROWSER.ERRORS_TO_SHOW_IN_BROWSER | Yes |
| serializedErrors | RSCプロトコルでシリアライズされたエラーのUint8Array | streamToUint8Array(errorsRscStream)の結果 | Yes |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| WebSocket接続 | htmlRequestId付きWebSocket接続確立 | 当該htmlRequestIdに対するエラーRSCストリームが`errorsRscStreamsByHtmlRequestId`マップに存在する場合 | serialized-errors.ts sendSerializedErrorsToClientForHtmlRequest |
| エラー発生 | サーバーサイドでエラーRSCストリームが生成された場合 | setErrorsRscStreamForHtmlRequestでストリームが登録されている場合 | serialized-errors.ts setErrorsRscStreamForHtmlRequest |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| エラーRSCストリーム未登録 | htmlRequestIdに対応するエラーRSCストリームが存在しない場合 |
| Pages Routerクライアント | Pages RouterではERRORS_TO_SHOW_IN_BROWSERは無視される |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[サーバーサイドエラー発生] --> B[エラーRSCストリーム生成]
    B --> C[setErrorsRscStreamForHtmlRequest登録]
    C --> D[WebSocket接続確立時]
    D --> E[sendSerializedErrorsToClientForHtmlRequest呼び出し]
    E --> F{htmlRequestIdのストリーム存在?}
    F -->|Yes| G[streamToUint8Arrayでバイナリ変換]
    G --> H[createBinaryHmrMessageDataでバイナリメッセージ構築]
    H --> I[client.sendでバイナリ送信]
    F -->|No| J[送信スキップ]
    I --> K[クライアント側処理]
    K --> L[ReadableStream構築]
    L --> M[createFromReadableStreamでデシリアライズ]
    M --> N[Error配列をconsole.errorで出力]
```

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

該当なし

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| シリアライズ失敗 | エラーRSCストリームの変換に失敗 | console.error('Failed to serialize errors.')で警告出力 |
| デシリアライズ失敗 | クライアント側でRSCストリームのパースに失敗 | console.error('Failed to deserialize errors.')で警告出力 |

### リトライ仕様

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

## 配信設定

### レート制限

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

### 配信時間帯

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

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

- 開発環境専用
- シリアライズされたエラーにはスタックトレースやファイルパスが含まれる
- バイナリ形式のためJSONと異なり内容が直接読み取りにくいが、RSCプロトコルで解読可能

## 備考

- このメッセージタイプは数値型のenum値（文字列ではない）であるため、`messages.ts`の`createBinaryHmrMessageData`関数によりバイナリ形式でエンコードされる
- `messages.ts`の`FAST_REFRESH_RUNTIME_RELOAD`定数はbinary messageの解析にも使用される
- Pages Routerではこのメッセージは無視される（hot-reloader-pages.ts 396-398行目）
- App Router（hot-reloader-app.tsx 514-536行目）では`createFromReadableStream`でError配列にデシリアライズし、各エラーを`console.error`で出力する
- エラーRSCストリームはhtmlRequestIdごとに管理され、送信後にマップからdeleteされる（serialized-errors.ts 39行目）
- TODOコメントとして、クライアントが接続しない場合（CURLやJavaScript無効時）のタイムアウトクリーンアップが未実装である旨が記載されている（serialized-errors.ts 48-49行目）
- `findSourceMapURL`がcreateFromReadableStreamのオプションとして渡され、ソースマップの解決に使用される

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | hot-reloader-types.ts | `packages/next/src/server/dev/hot-reloader-types.ts` | ErrorsToShowInBrowserMessage型定義。typeフィールド（数値）とserializedErrors（Uint8Array）フィールド |
| 1-2 | hot-reloader-types.ts | `packages/next/src/server/dev/hot-reloader-types.ts` | ERRORS_TO_SHOW_IN_BROWSER（数値型のenum値） |
| 1-3 | messages.ts | `packages/next/src/server/dev/messages.ts` | createBinaryHmrMessageData関数。バイナリメッセージのエンコード方式 |

**読解のコツ**: ERRORS_TO_SHOW_IN_BROWSERは他のHMRメッセージタイプ（文字列型）と異なり数値型であるため、バイナリプロトコルで送信される。

#### Step 2: サーバー側の送信ロジック

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | serialized-errors.ts | `packages/next/src/server/dev/serialized-errors.ts` | 7-11行目: errorsRscStreamsByHtmlRequestIdマップ |
| 2-2 | serialized-errors.ts | `packages/next/src/server/dev/serialized-errors.ts` | 12-27行目: sendSerializedErrorsToClient関数。streamToUint8ArrayでバイナリA変換し、ERRORS_TO_SHOW_IN_BROWSERメッセージを生成 |
| 2-3 | serialized-errors.ts | `packages/next/src/server/dev/serialized-errors.ts` | 29-42行目: sendSerializedErrorsToClientForHtmlRequest関数。htmlRequestIdでストリームを検索・送信 |
| 2-4 | serialized-errors.ts | `packages/next/src/server/dev/serialized-errors.ts` | 44-51行目: setErrorsRscStreamForHtmlRequest関数。エラーストリームの登録 |

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | hot-reloader-app.tsx | `packages/next/src/client/dev/hot-reloader/app/hot-reloader-app.tsx` | 514-536行目: ERRORS_TO_SHOW_IN_BROWSER処理。ReadableStreamを構築し、createFromReadableStreamでデシリアライズ |
| 3-2 | hot-reloader-pages.ts | `packages/next/src/client/dev/hot-reloader/pages/hot-reloader-pages.ts` | 396-398行目: Pages Routerでは無視される |

**主要処理フロー**:
- **515-521行目**: serializedErrorsからReadableStreamを構築
- **522行目**: `{ findSourceMapURL }`オプション付きでcreateFromReadableStream呼び出し
- **524-526行目**: デシリアライズされたError配列をconsole.errorで出力

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

```
[サーバー側]
エラー発生
    |
    +-- setErrorsRscStreamForHtmlRequest(htmlRequestId, errorsRscStream)
            |
            +-- errorsRscStreamsByHtmlRequestId.set(htmlRequestId, stream)

WebSocket接続確立
    |
    +-- sendSerializedErrorsToClientForHtmlRequest(htmlRequestId, sendToClient)
            |
            +-- errorsRscStreamsByHtmlRequestId.get(htmlRequestId)
            +-- errorsRscStreamsByHtmlRequestId.delete(htmlRequestId)
            +-- sendSerializedErrorsToClient(errorsRscStream, sendToClient)
                    |
                    +-- streamToUint8Array(errorsRscStream)
                    +-- sendToClient({ type: ERRORS_TO_SHOW_IN_BROWSER, serializedErrors })
                            |
                            +-- createBinaryHmrMessageData(message)
                            +-- client.send(binaryData)

[クライアント側 - App Router]
processMessage()
    |
    +-- case ERRORS_TO_SHOW_IN_BROWSER:
            +-- new ReadableStream({ start(controller) { ... } })
            +-- createFromReadableStream(stream, { findSourceMapURL })
            +-- .then((errors) => { for (const error of errors) console.error(error) })
```

### データフロー図

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

サーバーエラー               サーバー側                            ブラウザ (App Router)
                       ----> RSCストリーム生成
                             |
                             v
                       setErrorsRscStreamForHtmlRequest()
                       (htmlRequestIdごとに登録)
                             |
                             v
                       WebSocket接続確立時
                       sendSerializedErrorsToClient()
                             |
                             v
                       streamToUint8Array() -> バイナリ変換
                             |
                             v
                       createBinaryHmrMessageData()
                       [1byte type][N bytes data]
                             |
                             v
                       WebSocketバイナリ送信 ----------> processMessage()
                                                        |
                                                        v
                                                 ReadableStream構築
                                                        |
                                                        v
                                                 createFromReadableStream()
                                                 (RSCデシリアライズ)
                                                        |
                                                        v
                                                 Error[] -> console.error()
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| hot-reloader-types.ts | `packages/next/src/server/dev/hot-reloader-types.ts` | ソース | ErrorsToShowInBrowserMessage型定義 |
| serialized-errors.ts | `packages/next/src/server/dev/serialized-errors.ts` | ソース | エラーRSCストリーム管理・送信ロジック |
| messages.ts | `packages/next/src/server/dev/messages.ts` | ソース | createBinaryHmrMessageData - バイナリメッセージ構築 |
| hot-reloader-turbopack.ts | `packages/next/src/server/dev/hot-reloader-turbopack.ts` | ソース | WebSocket接続確立時のsendSerializedErrorsToClientForHtmlRequest呼び出し |
| hot-reloader-app.tsx | `packages/next/src/client/dev/hot-reloader/app/hot-reloader-app.tsx` | ソース | App RouterクライアントでのRSCデシリアライズ処理 |
| hot-reloader-pages.ts | `packages/next/src/client/dev/hot-reloader/pages/hot-reloader-pages.ts` | ソース | Pages Router（無視） |
| app-find-source-map-url.ts | `packages/next/src/client/app-find-source-map-url.ts` | ソース | findSourceMapURL - ソースマップ解決 |
