Next.jsで以下のコンポーネントをページに配置したところ、ブラウザのコンソールに「Hydration failed because the server rendered HTML didn't match the client」というエラーが出ました。原因として最も適切なものはどれですか? function Greeting() { return <p>現在時刻: {new Date().toLocaleTimeString()}</p>; }
解説
正解は「サーバーとクライアントで生成時刻が異なり、HTMLが一致しないから」です。Next.jsはまずサーバー側でHTMLを生成して返し、その後ブラウザ側で同じツリーをReactが組み立て直してイベントハンドラなどを紐付けます。この後者の処理がハイドレーション(hydration)です。Reactはこのとき、サーバーが返したHTMLとクライアントが生成した仮想DOMが一致することを前提にしているため、new Date() のように呼ぶたびに値が変わるものを描画すると、ほぼ確実にミリ秒単位でズレて警告が出ます。他の選択肢については、Dateはサーバーでも問題なく使えますし、useEffect の有無はハイドレーションの一致判定とは別の話です。ロケール差も原因になり得ますが、今回のように呼び出しタイミングが違えば、たとえロケールが同じでも値はズレます。ハイドレーションが「一致」を要求する理由サーバーから送られてくるHTMLは、ユーザーに素早く画面を見せるための静的なスナップショットです。一方クライアントのReactは、そのDOMを「自分が今レンダリングした結果」として引き取り、差分管理の起点にします。もし両者がズレていると、Reactはどのノードがどのコンポーネントに対応するか判断できず、最悪の場合DOMを丸ごと作り直してパフォーマンスが劣化します。だからこそ、初回レンダリングの結果は決定的(deterministic)でなければなりません。時刻や乱数を扱うときの定石「時刻に依存する表示をしたい」「Math.random()でIDを振りたい」といったケースでは、初回はサーバーと同じ値(または空)を返し、マウント後にuseEffectで更新する、というパターンが定番です。'use client'; import { useEffect, useState } from 'react'; export function Clock() { const [time, setTime] = useState<string | null>(null); useEffect(() => { setTime(new Date().toLocaleTimeString()); }, []); return <p>現在時刻: {time ?? '読み込み中...'}</p>; }useEffectはブラウザでしか実行されないため、サーバーでは null(=「読み込み中...」)が描画され、ハイドレーション後にクライアントだけが時刻に差し替えます。これでHTMLは一致したまま、動的な表示も実現できます。よくあるハイドレーションエラーの原因時刻・乱数・Math.random()など、呼び出すたびに値が変わるものtypeof window でクライアント判定して出し分ける処理localStorage や Cookie をレンダリング中に読む(サーバーには存在しない)<p>の中に<div>を入れるなど、不正なHTMLネスト(ブラウザが勝手に補正してDOMがズレる)抑制したいだけなら suppressHydrationWarning という属性もありますが、これは「ズレを承知で無視する」だけで根本解決にはなりません。エラーが出たらまず「この値はサーバーとクライアントで本当に同じになるか?」を疑うのが、ハイドレーションを理解した人の思考順です。