ハイドレーションエラー(Hydration Error)とは?原因とReact / Next.jsでの対処法を解説
ハイドレーションエラー(Hydration Error)は、サーバーサイドレンダリング(SSR)で生成したHTMLと、ブラウザで最初に描画したReactの結果が食い違ったときに発生する。React・Next.js・Astro などSSRを使うフレームワークで避けて通れないエラーで、コンソールには「Text content does not match server-rendered HTML.」「Hydration failed because the server rendered HTML didn’t match the client.」といったメッセージや、本番では「Minified React error #418」のような番号で表示される。この記事では、貼り付けたエラーメッセージから原因を逆引きし、useEffect・next/dynamic・suppressHydrationWarning を使った具体的な直し方までをまとめる。
まとめ:ハイドレーションエラーの要点
- 正体:サーバーが返したHTMLと、クライアント初回描画のReactツリーが一致しないと発生する。Reactは不一致部分を作り直すため、表示のちらつき・状態のリセット・パフォーマンス低下につながる。
- 最も多い原因:(1)
Date()・Math.random()など描画のたびに変わる値、(2)<p>の中に<div>を入れるなどの不正なHTMLネスト、(3) Grammarly や ColorZilla などブラウザ拡張によるDOM改変。 - 最短の直し方:クライアント固有の値は
useEffect後にセットする、SSR不要な部品はnext/dynamicでssr: falseにする、不正なタグネストを直す。suppressHydrationWarningは根本原因を消さない逃げ道なので乱用しない。
ハイドレーションエラーとは(ハイドレーションとSSRの仕組み)
ハイドレーション(Hydration)とは、サーバーが事前生成したHTMLに対して、ブラウザ側のReactがイベントハンドラや状態を付け直して「操作できるアプリ」に変える処理を指す。SSRやSSG(静的生成)では、まず素早く表示できるHTMLをサーバーで作り、その上にReactがクライアントで同じツリーを再現して結び付ける。この「サーバーのHTML」と「クライアント初回描画」がずれると、ハイドレーションエラーになる。Next.jsでのSSR/CSR/SSGの違いはNext.jsにおける主要なレンダリング方法の種類と概要で整理している。
ハイドレーションが行っていること
ReactはhydrateRootでHTMLを再利用しようとする。既存のDOMを作り直さず、そこに仮想DOMを重ねてイベントを結線するのがハイドレーションだ。だからこそ、サーバーが出力したマークアップとクライアントの初回レンダリング結果が同じであることが前提になる。
不一致が起きるとツリーが作り直される理由
初回描画の結果がサーバーHTMLと一致しないと、Reactはそのツリーを信用できず、クライアント側で描画し直す。React 18以降は該当ツリーを再生成して回復するが、その分だけ画面のちらつき・入力状態のリセット・初期表示の遅延が起きる。React 19では不一致箇所の差分を分かりやすく表示するようエラーが改善された(React 19とは?18からの変更点・新機能とReact Compiler・最新バージョンを解説)。
エラーメッセージ別の意味(React error #418 / #423 / #425)
本番ビルドではメッセージが省略され「Minified React error #◯◯」とだけ出る。番号とメッセージの対応を押さえておくと、貼り付けられたエラーから原因を絞り込める。代表的なものを整理する。
| エラーメッセージ / 番号 | 意味 | 主な原因 |
|---|---|---|
| Text content does not match server-rendered HTML. (#425) |
テキスト内容がサーバーとクライアントで違う | 時刻・乱数・ロケール依存の文字列 |
| Hydration failed because the server rendered HTML didn’t match the client. (#418) |
描画結果が一致せずツリーを再生成 | 条件分岐・動的値・不正なネスト |
| There was an error while hydrating but React was able to recover by instead client rendering the entire root. (#423) |
回復したがroot全体をクライアント描画にフォールバック | ハイドレーション不一致からroot全体の再描画で回復 |
| A tree hydrated but some attributes of the server rendered HTML didn’t match the client properties. This won’t be patched up. | 属性(class/style等)の不一致。修復されない | ブラウザ拡張のDOM改変・CSS-in-JS設定 |
| In HTML, <html> cannot be a child of <body>. This will cause a hydration error. | HTMLの入れ子が仕様違反 | 不正なタグネスト(後述) |
本番の「Minified React error #◯◯」を読む方法
番号付きメッセージのURL(例:react.dev/errors/418)を開くか、開発ビルド(NODE_ENV=development)で動かすと、省略されていた完全なメッセージと該当コンポーネントが表示される。原因調査は本番のminified表示のまま進めず、まず開発ビルドで再現してメッセージを復元するのが早い。
ハイドレーションエラーが起きる主な原因
Next.js公式が挙げる典型的な原因は次の通り。いずれも「サーバーとクライアントで描画結果が変わる」ことが共通点だ。
| 原因 | 具体例 | 対処の方向 |
|---|---|---|
| 時刻・タイムゾーン・ロケール | new Date()、toLocaleString() |
useEffectでクライアント描画 |
| 毎回変わる値 | Math.random()、乱数ID |
useEffect / 固定シード |
| ブラウザ専用API | window、localStorage、typeof window分岐 |
useEffect後に参照 |
| 不正なHTMLネスト | <p>内の<div>、<a>の入れ子 |
マークアップを修正 |
| ブラウザ拡張 | Grammarly、ColorZilla(cz-shortcut-listen) |
拡張の切り分け・body限定の抑制 |
| CDN / CSS-in-JS 設定 | CDN/プロキシのHTML自動改変、設定ミスのCSS-in-JS | 自動改変の無効化・公式設定 |
特に多いのが、描画のなかで「サーバーでは決まらない値」を直接使うケースだ。次のコードはサーバーとクライアントで時刻が変わるため、ほぼ確実にエラーになる。
// NG: 描画中に毎回変わる値をそのまま使う
function Clock() {
// サーバーが返した時刻とクライアント描画時の時刻がずれる
return <p>{new Date().toLocaleTimeString()}</p>;
}
ハイドレーションエラーの対処法
原因は違っても、直し方は「サーバーとクライアントの初回描画を一致させる」か「一致しない部分をハイドレーションの対象から外す」のどちらかに集約できる。
useEffectでのクライアント限定描画
クライアントでしか決まらない値は、初回描画では出さずuseEffect実行後にセットする。useEffectはハイドレーション後に呼ばれるため、windowなどのブラウザAPIをずれなく使える。React公式・Next.js公式ともに推奨する基本パターンだ。
import { useState, useEffect } from 'react';
export default function Clock() {
const [time, setTime] = useState(null);
useEffect(() => {
// クライアントでのみ実行されるので不一致にならない
setTime(new Date().toLocaleTimeString());
}, []);
return <p>{time ?? '読み込み中'}</p>;
}
useStateやuseEffectの基本はReactフックの基本と導入:React 16.8で追加された新機能の概要を参照。
next/dynamicによるSSR無効化
グラフ描画やブラウザAPI前提の部品など、そもそもサーバーで描く必要がないコンポーネントは、Next.jsのdynamicでssr: falseにしてハイドレーション対象から外す。
import dynamic from 'next/dynamic';
// この部品はクライアントでのみ描画される
const NoSSR = dynamic(() => import('./Chart'), { ssr: false });
export default function Page() {
return <NoSSR />;
}
不正なHTMLネストの修正
「<html> cannot be a child of <body>」「<div> cannot be a child of <p>」のように出る場合は、HTMLの入れ子がブラウザの正規化で書き換えられ、サーバー出力とずれている。<p>の中にブロック要素を置かない、<a>や<button>を入れ子にしない、といった修正で解消する。
// NG: <p> の中にブロック要素は置けない(ブラウザが勝手に閉じる)
<p><div>本文</div></p>
// OK: 入れ替える
<div><p>本文</p></div>
ブラウザ拡張・CDN由来の不一致の切り分け
「some attributes … didn’t match」でcz-shortcut-listenやdata-gr-などの見慣れない属性が出る場合、原因は自分のコードではなくブラウザ拡張(ColorZilla・Grammarly等)がDOMを書き換えているケースが多い。シークレットウィンドウや拡張を無効にした状態で再現するか確認し、コード側の問題と切り分ける。<body>への注入が避けられない場合は<body>に限定してsuppressHydrationWarningを付ける。CDN/プロキシがHTMLを自動改変している場合(旧CloudflareのAuto Minify等)はその設定を無効化する。
suppressHydrationWarningは最後の逃げ道(乱用しない)
タイムスタンプのように、サーバーとクライアントで値が必然的に異なる箇所はsuppressHydrationWarningで警告を抑制できる。ただしこれは不一致を「なかったこと」にするだけで、根本原因は直らない。1階層しか効かず、テキストの不一致は修復されない。使ってよいのは「拡張機能による属性注入」「意図的なクライアント専用の日時表示」など原因が明確な場合に限る。原因不明のエラーをこれで握りつぶすと、状態のずれやSEO上の表示差が残り続けるので避けるべきだ。
// 逃げ道: タイムスタンプなど1階層のみ。根本原因は直らない
<time dateTime="2026-07-18" suppressHydrationWarning>
{new Date().toLocaleDateString()}
</time>
ハイドレーションエラーを防ぐチェックリスト
設計・実装の段階で次を守っておくと、そもそもエラーが出にくくなる。
- 描画(return内)で
Date()・Math.random()・window・localStorageを直接使わない。動的な値はuseEffect後にセットする。 - SSRが不要な部品は
next/dynamicのssr: falseで最初から外す。 <p>にブロック要素を入れない、<a>/<button>を入れ子にしないなど、HTML仕様に沿ったマークアップにする。- CSS-in-JS(styled-components・Emotion等)はフレームワーク公式のSSR設定に従う。
- iOSが電話番号やメールを自動リンク化して不一致になる場合は、
<meta name="format-detection" content="telephone=no, date=no, email=no, address=no">を追加する。 - ブラウザ拡張はユーザー環境の問題なので、切り分けたうえで必要最小限の
suppressHydrationWarningに留める。
よくある質問
ハイドレーションエラーを放置するとどうなりますか?
React 18以降は該当ツリーをクライアントで描画し直して回復するため、画面が真っ白になることは通常ない。ただし初回表示のちらつき、入力状態のリセット、描画の二度手間によるパフォーマンス低下が起きる。SSRの利点(初期表示の速さ・SEO)を損なうので、放置せず原因を直すべきだ。
開発環境では詳しいメッセージが出るのに、本番では「Minified React error」だけです
本番ビルドはメッセージを省略して番号だけ表示する仕様のため。番号のURL(react.dev/errors/418 など)を開くか、開発ビルドで再現すると完全なメッセージと該当箇所が分かる。
suppressHydrationWarningを付ければ直りますか?
警告が消えるだけで、サーバーとクライアントの不一致そのものは解消しない。1階層しか効かず、テキスト不一致は修復されない。原因が明確な箇所(タイムスタンプ・拡張機能の属性注入)だけに使い、原因不明のエラーには使わない。
ブラウザ拡張が原因かどうかを切り分けるには?
シークレットウィンドウや拡張機能をすべて無効にした状態で再現するかを確認する。拡張を切ると出なくなり、cz-shortcut-listenやdata-gr-のような属性が付いていれば、コードではなく拡張が原因だと判断できる。
Astroなど他のSSRフレームワークでも起きますか?
起きる。サーバーHTMLとクライアント描画を突き合わせる仕組みは共通のため、原因も対処も同じ考え方だ。Astroではclient:*ディレクティブでハイドレーションの範囲を制御し、クライアント専用の部品を明示的に指定して不一致を避ける。