定義
ハイドレーション不一致(hydration mismatch)とは、サーバーがレンダーした HTML とクライアントの初回レンダー結果が異なり、フレームワークが既存 DOM にイベントハンドラを再付着(hydrate)する過程でツリーを一致させられない状況だ。初回レンダーでサーバーにない値(localStorage、window、ランダム、現在時刻)を使うときに典型的に発生する。
なぜ必要か
SSR(サーバーサイドレンダリング:リクエストごとにサーバーが HTML を作って送る)/SSG(静的サイト生成:ビルド時点で HTML をあらかじめ作っておく)は、HTML をサーバーで先に作って送りユーザーが素早くコンテンツを見られるようにし(critical-rendering-path)、ブラウザで同じツリーを React が再付着(hydrate)して相互作用を付ける。この「サーバーマークアップ == 初回クライアントレンダー」という前提が壊れると警告が出て、ちらつき(flicker)や DOM の破棄・再生成が起き、パフォーマンスと視覚的安定性が悪化する。したがってクライアント専用の状態をいつ反映するかが核心だ。
動作原理
サーバーには window・localStorage がない。だからコンポーネントが初回レンダーでこうした値を読むと、サーバーはデフォルト値で、クライアントは保存された値でレンダーし、両者が分かれる。React は hydrate 時に二つのツリーを比較し、差異を見つけて警告する。
| 戦略 | 方法 | トレードオフ |
|---|---|---|
| マウント後に 1 回反映 | 初回レンダーは SSR と同じデフォルト値、useEffect でクライアント値を適用 | 1 フレームのちらつきの可能性 |
| 同期インラインスクリプト | hydrate 前にインライン <script> が DOM 属性を直接セット | ちらつきなし、ボイラープレート増加 |
| 意図された不一致の抑制 | 時刻など本質的に異なる値だけ suppressHydrationWarning | 本当のバグを隠してはいけない |
実務適用
ちらつきまで消す必要があれば、hydrate 前に実行されるインラインスクリプトで DOM 属性を直接セットしてから、React がその上に再付着するようにする。永続レイアウト値(localStorage など)はストア初期化時点ではなくマウント後に読むのが基本ルールだ。
トレードオフ
- マウント後の反映は実装がシンプルだが、デフォルト値 → 実際の値への遷移で 1 フレームのちらつきが見えることがある。
- インラインスクリプトはちらつきがないがコードが増え、スクリプトと React 状態を食い違わせないよう管理する必要がある。
使ってはいけない場合
- CSR 専用アプリ(サーバーレンダーがなければ不一致自体がない)。
- 値がサーバーでも決定できる場合 — わざわざクライアント専用に先送りせずサーバーでレンダーする。
よくある間違い
- 初回レンダー本体で
localStorage/window/Date.now()/ランダムを直接使う。 suppressHydrationWarningを広範に付けて、本当の不一致バグを隠す。- ストアを
localStorageで初期化して(初回レンダーからクライアント値)サーバーと食い違う。
関連概念
- critical-rendering-path — サーバーが作った HTML が先に描画され、その後 JS が再付着するレンダリング順序。