React のハイドレーションエラー(Hydration mismatch)が解消できない
SSR が出力した HTML とクライアント側の初回レンダリング結果が食い違うと Hydration mismatch が出る。
new Date() や Math.random() の JSX 直書き、window 依存、HTML 入れ子違反、ブラウザ拡張による DOM 書き換えが主因。
#react#hydration#ssr#nextjs#mismatch
要約
Hydration mismatch は サーバ側でレンダリングした HTML と、クライアント側の React の初回描画結果が一致しない ときに出る。new Date() の直書き、window 参照、無効な HTML 入れ子、ブラウザ拡張による DOM 書き換えが主因。初回はサーバと同じ内容を描画し、差異は useEffect 後に反映する のが基本戦略。
よくある原因
- 時刻・乱数を JSX に直接書いている:
<p>{new Date().toLocaleString()}</p>はサーバ実行時刻とクライアント実行時刻がズレて mismatch する。 - window 依存の分岐:
typeof window !== 'undefined' ? <A /> : <B />のような書き方はサーバとクライアントで異なる枝を選ぶため必ず食い違う。 - 無効な HTML ネスト:
<p>内の<div>、<button>内の<button>、<a>の入れ子などはブラウザが閉じタグを補ってツリー構造が変わる。 - ブラウザ拡張による DOM 改変: Grammarly やパスワード拡張が
<body>属性を書き換えると、最上位ノードで mismatch が起きる。
解決策
1. ランダム値・時刻は useEffect で
function Clock() {
const [now, setNow] = useState<string | null>(null);
useEffect(() => {
setNow(new Date().toLocaleString());
}, []);
return <p>{now ?? "..."}</p>; // SSR でも "..." を出す
}サーバ HTML とクライアント初回描画を 同じ文字列 にしてから、Effect で書き換える。
2. クライアント専用 UI は isClient で守る
const [isClient, setIsClient] = useState(false);
useEffect(() => setIsClient(true), []);
if (!isClient) return null;
return <OnlyOnClient />;公式の hydrateRoot ドキュメント のとおり、サーバとクライアントが同一木を作る前提に立ち返り、差異は描画後に反映する。
3. 無効な HTML 入れ子を直す
DevTools のコンソールに In HTML, <div> cannot be a descendant of <p>. のように具体的な要素名と場所が出る。
ブラウザのオートクローズで木構造が変わるため、必ず直す。
4. suppressHydrationWarning(限定的)
時刻表示など、本文と乖離が起こりうると承知の上で抑止する場合のみ。
<time suppressHydrationWarning>{new Date().toISOString()}</time>公式の説明 のとおり、不一致を「無視する」だけで根本解決ではない。
子要素にむやみに広げない。