# 通知設計書 35-ハイドレーションエラー通知

## 概要

本ドキュメントは、Next.js開発サーバーにおけるハイドレーションエラー通知の設計について記載する。クライアント/サーバーのHTML不一致（ハイドレーションエラー）検出時に詳細な差分情報を含むオーバーレイを表示する通知機能である。

### 本通知の処理概要

ハイドレーションエラー通知は、サーバーサイドレンダリング（SSR）で生成されたHTMLとクライアントサイドのReactによるハイドレーション結果が一致しない場合に、詳細な差分情報を視覚的に表示する機能である。

**業務上の目的・背景**：ハイドレーションエラーはSSRとCSRの不一致から生じるバグの原因であり、デバッグが困難なことが多い。本通知は、サーバーとクライアントのHTML差分をビジュアルに表示することで、不一致の原因箇所を迅速に特定できるようにする。React 19以降では、コンポーネントスタック差分が提供されるため、より精度の高い情報が得られる。

**通知の送信タイミング**：Reactのハイドレーション処理でサーバーレンダリング結果とクライアントレンダリング結果の不一致が検出された場合に表示される。`isHydrationError()`関数でエラーメッセージのパターンマッチにより判定される。

**通知の受信者**：開発中のアプリケーションをブラウザで表示している開発者。

**通知内容の概要**：ハイドレーションエラーのメッセージ、サーバー/クライアント間のHTML差分（PseudoHtmlDiffコンポーネント）、Reactが提供するコンポーネントスタック差分、Next.jsのハイドレーションエラーに関するドキュメントリンクが表示される。

**期待されるアクション**：開発者はHTML差分を確認し、サーバーとクライアントで異なる出力を生じさせているコンポーネントを特定する。ブラウザ固有のAPI使用、`Date.now()`のような非決定的な値、条件付きレンダリングの問題などを修正する。

## 通知種別

ブラウザUI / Dev Overlay（ランタイムエラーオーバーレイの特殊表示）

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | 非同期（ランタイムエラーとして捕捉後、ハイドレーションエラー判定で特殊表示） |
| 優先度 | 高 |
| リトライ | 無し |

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

ランタイムエラーオーバーレイの一部として動作する。`useErrorDetails`フック内で`getHydrationErrorDetails()`が呼ばれ、エラーがハイドレーションエラーであるかを判定する。判定にはPages Router用の`getSquashedHydrationErrorDetails()`とApp Router用の`isHydrationError()`の2つの経路がある。

## 通知テンプレート

### ブラウザUI表示の場合

| 項目 | 内容 |
|-----|------|
| ヘッダーラベル | Runtime Error（通常のランタイムエラーと同じ） |
| エラーメッセージ | ハイドレーションエラーメッセージ（HydrationErrorDescription経由） |
| 差分表示 | PseudoHtmlDiffコンポーネントでサーバー/クライアントHTML差分 |
| 追加情報リンク | https://nextjs.org/docs/messages/react-hydration-error |
| 形式 | HTMLオーバーレイ |

### 本文テンプレート

```
[Runtime Error]
{hydrationErrorMessage}

{notes}

See more info here: https://nextjs.org/docs/messages/react-hydration-error

--- HTML Diff ---
+ {serverRenderedHTML}
- {clientRenderedHTML}
```

### 添付ファイル

該当なし

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| warning | ハイドレーションエラーメッセージ | HydrationErrorDetails.warning | No |
| notes | 追加のノート情報 | HydrationErrorDetails.notes | No |
| reactOutputComponentDiff | React出力のコンポーネント差分 | HydrationErrorDetails.reactOutputComponentDiff | No |
| NEXTJS_HYDRATION_ERROR_LINK | ドキュメントリンク | 定数 | Yes |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| ランタイムエラー | ハイドレーション不一致検出 | isHydrationError(error) === true | Reactがサーバー/クライアントのHTML不一致を検出した場合 |
| ランタイムエラー | Pages Routerハイドレーションエラー | getSquashedHydrationErrorDetails(error) !== null | Pages Router固有のハイドレーションエラー検出 |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| isHydrationError() === false かつ getSquashedHydrationErrorDetails() === null | エラーがハイドレーションエラーでない場合は通常のランタイムエラー表示となる |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[ランタイムエラー発生] --> B[useErrorDetails呼び出し]
    B --> C[getHydrationErrorDetails呼び出し]
    C --> D{Pages Router?}
    D -->|Yes| E[getSquashedHydrationErrorDetails]
    D -->|No| F{isHydrationError?}
    E --> G{結果 !== null?}
    G -->|Yes| H[HydrationErrorDetails生成]
    G -->|No| F
    F -->|Yes| I[getHydrationErrorStackInfo]
    F -->|No| J[ハイドレーションエラーではない]
    I --> K{message !== null?}
    K -->|Yes| H
    K -->|No| J
    H --> L[HydrationErrorDescription表示]
    L --> M{reactOutputComponentDiff存在?}
    M -->|Yes| N[PseudoHtmlDiff表示]
    M -->|No| O[差分なし]
    N --> P[notes表示]
    O --> P
    P --> Q[ドキュメントリンク表示]
```

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

### 参照テーブル一覧

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

### 更新テーブル一覧

該当なし

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| ハイドレーションエラー判定失敗 | エラーメッセージのパターンが変更された場合 | 通常のランタイムエラーとして表示される（フォールバック） |
| 差分情報の欠如 | React 18以前でコンポーネントスタック差分が提供されない場合 | reactOutputComponentDiffがnullの場合、PseudoHtmlDiff表示をスキップ |

### リトライ仕様

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

## 配信設定

### レート制限

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

### 配信時間帯

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

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

- ハイドレーションエラーにはサーバーレンダリングされたHTMLの断片が含まれるが、開発環境のみで使用されるため問題はない
- Dev Overlayは本番ビルドには含まれない

## 備考

- ハイドレーションエラーの判定は以下の正規表現パターンで行われる：
  - `In HTML, (.+?) cannot be a child of <(.+?)>`
  - `In HTML, (.+?) cannot be a descendant of <(.+?)>`
  - `In HTML, text nodes cannot be a child of <(.+?)>`
  - `In HTML, whitespace text nodes cannot be a child of <(.+?)>`
  - `Hydration failed because the server rendered (text|HTML) didn't match the client`
  - `A tree hydrated but some attributes of the server rendered HTML didn't match the client properties`
- React 19+では、エラーメッセージにコンポーネントスタック差分が含まれる（`\n\n`で区切られる）
- `REACT_HYDRATION_ERROR_LINK`（`https://react.dev/link/hydration-mismatch`）を区切りとして差分情報を抽出する

---

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

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

### 推奨読解順序

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

まず、ハイドレーションエラーの型定義と判定ロジックを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | react-19-hydration-error.ts | `packages/next/src/next-devtools/shared/react-19-hydration-error.ts` | 定数定義（1-4行目）、エラーパターン正規表現（9-14行目）、`isHydrationError()`（16-26行目）、`getHydrationErrorStackInfo()`（32-79行目） |
| 1-2 | errors.tsx | `packages/next/src/next-devtools/dev-overlay/container/errors.tsx` | `HydrationErrorDetails`型（465-470行目）。`warning`, `notes`, `reactOutputComponentDiff`フィールドを理解する |

**読解のコツ**: `getHydrationErrorStackInfo()`は2つの経路でメッセージを解析する。エラーメッセージにコンポーネントスタック差分が含まれる場合（React 19+）と、`REACT_HYDRATION_ERROR_LINK`で区切られる場合がある。

#### Step 2: エントリーポイントを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | errors.tsx | `packages/next/src/next-devtools/dev-overlay/container/errors.tsx` | `useErrorDetails`フック（487-511行目）。エラーの種類を判定する起点 |

**主要処理フロー**:
1. **496-502行目**: `getHydrationErrorDetails()`でハイドレーションエラー判定
2. **504-507行目**: `getBlockingRouteErrorDetails()`でブロッキングルートエラー判定
3. **509行目**: どちらでもない場合は`noErrorDetails`を返す

#### Step 3: ハイドレーションエラー判定を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | errors.tsx | `packages/next/src/next-devtools/dev-overlay/container/errors.tsx` | `getHydrationErrorDetails()`（513-543行目）。Pages Router用とApp Router用の2つの判定経路 |

**主要処理フロー**:
- **517-526行目**: Pages Router用の`getSquashedHydrationErrorDetails()`呼び出し
- **528-530行目**: `isHydrationError(error)`でApp Router用の判定
- **532行目**: `getHydrationErrorStackInfo(error)`でメッセージ・差分・ノートを抽出

#### Step 4: 表示コンポーネントを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | errors.tsx | `packages/next/src/next-devtools/dev-overlay/container/errors.tsx` | `HydrationErrorDescription`（42-44行目）と`Errors`内のcase 'hydration'（702-741行目）。メッセージ・ノート・差分の表示ロジック |

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

```
Errors [errors.tsx:584]
    |
    +-- useErrorDetails() [errors.tsx:487]
    |      +-- getHydrationErrorDetails() [errors.tsx:513]
    |      |      +-- getSquashedHydrationErrorDetails() [Pages Router]
    |      |      +-- isHydrationError() [react-19-hydration-error.ts:16]
    |      |      +-- getHydrationErrorStackInfo() [react-19-hydration-error.ts:32]
    |      |
    |      +-- getBlockingRouteErrorDetails() [errors.tsx:545]
    |
    +-- HydrationErrorDescription [errors.tsx:42]
    |      +-- HotlinkedText
    |
    +-- PseudoHtmlDiff [component-stack-pseudo-html.tsx]
```

### データフロー図

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

React Hydration Error         useErrorDetails()                  ブラウザ
(Error object with    ------> isHydrationError()           ----> オーバーレイ
 mismatch message)            getHydrationErrorStackInfo()       (エラーメッセージ +
                              HydrationErrorDetails生成           HTML差分 +
                                                                  ドキュメントリンク)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| react-19-hydration-error.ts | `packages/next/src/next-devtools/shared/react-19-hydration-error.ts` | ソース | ハイドレーションエラー判定、メッセージ解析、定数定義 |
| errors.tsx | `packages/next/src/next-devtools/dev-overlay/container/errors.tsx` | ソース | Errorsコンポーネント、useErrorDetails、HydrationErrorDescription |
| component-stack-pseudo-html.tsx | `packages/next/src/next-devtools/dev-overlay/container/runtime-error/component-stack-pseudo-html.tsx` | ソース | PseudoHtmlDiffコンポーネント。HTML差分のビジュアル表示 |
