Next.jsにおけるReactハイドレーションエラー418の修正:テキスト不一致と拡張機能(2026年版)

Table of Contents
ブラウザのコンソールに巨大な赤いハイドレーションエラーが表示されたときのことを今でも覚えています。メッセージは不可解で、スタックトレースは縮小されたReactファイルを指しており、UIは一瞬完全に壊れてから元の状態に戻りました。
データベースがダウンしたのだと思いましたが、そうではありませんでした。コンポーネント内で動的なタイムゾーン文字列を直接レンダリングしようとしただけでした。
Next.js Hydration Error Matcher & Fixer
Diagnose Error #418, #423, #425 & get copy-paste code diffs
Paste Next.js or React hydration error logs to instantly diagnose server-client HTML mismatches (dates, localStorage, extension injection) and generate safe fixes.
早見表:4つの原因と解決策
| エラー | 根本原因 | 修正 |
|---|---|---|
| テキストコンテンツの不一致 | window / localStorage のレンダリング | useEffect、useState(false) の初期化に移動 |
| エラー 418 | サーバーで直接レンダリングされた日付/時刻 | suppressHydrationWarning または useEffect |
| ダークモードのちらつき | ハイドレーション前にテーマが読み込まれる | Reactが読み込まれる前のインラインスクリプト |
<html> の不一致 | ブラウザ拡張機能による属性の挿入 | suppressHydrationWarning を <html> に適用 |
Next.js(または任意のSSR Reactフレームワーク)で構築している場合、これらのうち少なくとも1つに遭遇するでしょう。以下のセクションでは、それぞれの原因と、そのままコピー&ペーストできる修正方法を説明します。

Modern Next.js Architecture Series
ライブハイドレーションエラーマッチャー&コードフィクサー
ブラウザコンソールのエラーログまたはJSXスニペットを以下に直接貼り付けて、正確な根本原因を診断し、即座にコピー&ペーストで修正を入手してください。
「ハイドレーション」とは一体何か?
エラーを修正する前に、そのプロセスを理解する必要があります。ユーザーがページをリクエストすると、Next.jsは次の2つのことを行います。
- プリレンダリング(サーバーサイド): Next.jsはサーバー上でReactコンポーネントを実行し、生のHTMLを生成してブラウザに送信します。ユーザーはページを即座に確認できますが、まだインタラクティブではありません(ボタンは機能せず、入力は不活性です)。
- ハイドレーション(クライアントサイド): Reactはクライアント上で実行され、JavaScriptバンドルをダウンロードし、サーバーでレンダリングされたHTML構造を走査し、イベントリスナー(
onClickなど)をアタッチしてページをインタラクティブにします。
SSRの黄金律は次のとおりです。サーバーで生成されたHTMLは、クライアントの最初のレンダリングで生成されたHTMLと完全に一致しなければなりません。
たとえ1文字でも違いがあれば、Reactはパニックに陥ります。ハイドレーションの不一致エラーをスローし、サーバーのHTMLを破棄して、ページを最初から再構築します。これにより、サイトの速度が低下し、SSRのパフォーマンス上の利点が損なわれます。
| フェーズ | 実行場所 | 出力 | 責任 |
|---|---|---|---|
| プリレンダリング | Node.jsサーバー | 生のHTMLとCSS | 即座の視覚的描画 |
| ハイドレーション | ユーザーブラウザ | イベントバインドされたDOM | インタラクティブなページ(アクティブ) |
「テキストコンテンツがサーバーレンダリングされたHTMLと一致しません」(window & localStorage)を修正する方法
「テキストコンテンツがサーバーレンダリングされたHTMLと一致しません」というエラーの最も一般的な原因は、コンポーネントのレンダリングパスでブラウザAPIを直接参照することです。サーバーサイドのプリレンダリング中、Node.jsはwindow、document、またはlocalStorageの概念を持っていません。
// ❌ THIS WILL CRASH ON THE SERVER OR MISMATCH
function MyComponent() {
const width = window.innerWidth; // window is undefined on the server!
return <div>Width: {width}</div>;
}
ハードクラッシュを防ぐためにtypeof window !== 'undefined'チェックでラップしても、サーバー出力(<div>Desktop</div>)がブラウザが最初の描画でレンダリングするものと異なるため、ハイドレーションエラーが発生します。
1-// ❌ ハイドレーションエラー: サーバーは "Server" を取得し、クライアントは幅を取得1+// ✅ 安全: 状態はクライアントマウント時に初期化される2+import { useState, useEffect } from 'react';3+24 function ResponsiveLayout() {3- const isMobile = typeof window !== 'undefined' && window.innerWidth < 768;5+ const [isMobile, setIsMobile] = useState(false);6+7+ useEffect(() => {8+ setIsMobile(window.innerWidth < 768);9+ }, []);10+411 return <div>{isMobile ? 'Mobile' : 'Desktop'}</div>;512 }
フック戦略
ブラウザAPIの場合、常に状態の更新をuseEffectに遅延させてください。useEffectはマウント後にブラウザでのみ実行されるため、サーバーでは実行されません。初期HTMLは一致し、クライアントはその後安全にUIを更新します。
Reactエラー #418 と動的コンテンツ(日付、タイムスタンプ、Math.random)を修正する方法
縮小されたReactエラー #418 は、サーバーでレンダリングされたHTMLテキストがクライアントでレンダリングされたテキストと異なる場合に発生します。JSXで現在のタイムスタンプまたは乱数をレンダリングすると、サーバーは1つの文字列(ビルド時またはリクエスト時など)を出力しますが、クライアントブラウザは数ミリ秒後に異なる文字列を出力します。
// ❌ Server: "Rendered at 10:00:00", Client: "Rendered at 10:00:02" -> Error #418
function CurrentTime() {
return <div>Time: {new Date().toLocaleTimeString()}</div>;
}
必要なものに応じて、これを解決する方法は2つあります。
解決策A:クライアントマウントへの遅延(推奨)
SEOのためにデータが必要ない場合は、コンポーネントがマウントされるまで表示を待機します。
マウント状態の初期化
コンポーネントがブラウザにマウントされたかどうかを追跡するためのブール値の状態を作成します。
useEffectでトリガー
useEffect内でブール値をtrueに反転させます。
条件付きレンダリング
サーバーではローディングプレースホルダーを表示し、クライアントでは実際の日付を表示します。
import { useState, useEffect } from 'react';
export default function CurrentTime() {
const [mounted, setMounted] = useState(false);
useEffect(() => {
setMounted(true);
}, []);
if (!mounted) return <div>Loading time...</div>;
return <div>Time: {new Date().toLocaleTimeString()}</div>;
}
解決策B:警告の抑制(静的コンテンツの場合)
不一致が無害な場合(静的なタイムゾーンのレンダリングや意図的な不一致など)は、suppressHydrationWarning属性を使用してReactに無視するように指示できます。
// This disables the console error for this specific element
<div suppressHydrationWarning>Time: {new Date().toLocaleTimeString()}</div>
警告
suppressHydrationWarningは1レベルの深さでしか機能せず、Reactの不一致再構築による根本的なパフォーマンスコストを修正するものではありません。単純なテキストノード(日付やタイムスタンプなど)に限定して使用し、大規模なUIツリーには使用しないでください。
無効なHTMLタグのネストの不一致(<div> 内の <p>)を修正する
無効なHTMLタグのネストは、ハイドレーションエラーの最も厄介な原因です。JavaScriptの状態やタイミングとは関係なく、ブラウザのDOMパーサーが無効なHTML構文を自動的に修復する方法に関係しています。
<p>タグ内に<div>をネストすると、ブラウザのHTMLパーサーは<div>が開かれる前に<p>タグを自動的に閉じます。これにより、意図したDOM構造がまったく異なるものに変換されます。
Reactは、ブラウザによって解析されたDOM構造が、サーバーで生成された仮想DOMツリーと一致することを期待しています。ブラウザが強制的にタグを書き換えたり閉じたりすると、Reactは不一致のツリー深度を検出し、"In HTML, <div> cannot be a descendant of <p>"をスローします。
1-// ❌ 無効なHTML: ブラウザは <div> の前に <p> を強制的に閉じる1+// ✅ 有効なHTML: 代わりに div または span を使用する2-<p>2+<div>33 Check this out:44 <div>Nested container</div>5-</p>5+</div>
ハイドレーションエラーを引き起こす一般的なHTMLネストの誤り:
<p>内に<div>または<p>を配置する<p>内に<ul>または<ol>を直接配置する<table>の直下に<tr>を配置する代わりに、<tbody>でラップする- インライン要素(
a、span)内にブロック要素(h1、form、section)を配置する
ハイドレーションエラーのデバッグ方法:ステップバイステップ
Next.js 14以降では、以前よりもはるかに優れたエラーオーバーレイが提供されていますが、トレースは依然として不可解な場合があります。ここでは、私が毎回使用する正確なワークフローを紹介します。
ステップ1:オーバーレイのエラー差分を読む
開発中にハイドレーションエラーが発生すると、Next.jsはサーバー出力とクライアント出力を並べて表示する赤いオーバーレイを表示します。最初に行うべきことは、両方を注意深く読むことです。
Server rendered:
<div class="theme-dark">
Client rendered:
<div class="theme-light">
これは、不一致がテーマ関連であることをすぐに示しています。レンダリング中に読み取られているlocalStorageまたはcookiesを探してください。
ステップ2:拡張機能を無効にしてシークレットモードでテストする
ブラウザ拡張機能は、サイレントなハイドレーションキラーです。広告ブロッカー、文法チェッカー、翻訳拡張機能は、Reactがハイドレーションする前にDOMノードを挿入したり、属性を変更したりします。サーバーはこれらの変更を認識していないため、Reactはパニックに陥ります。
常にシークレットウィンドウでエラーを再現してください。エラーがシークレットモードで消える場合は、ブラウザ拡張機能が原因であり、コードではありません。
ステップ3:より良い差分を得るためにNext.js Turbopackを有効にする
next dev --turbopack
Turbopackは、デフォルトのWebpackモードよりも正確なハイドレーションエラーメッセージを提供します。これには、正確なコンポーネントパスと不一致の文字レベルの差分が含まれます。
ステップ4:一時的にsuppressHydrationWarningで二分探索する
原因が見つからない場合は、一時的にルートの<body>にsuppressHydrationWarningを追加します。これにより、すべてのハイドレーションエラーが抑制されます。次に、エラーが再表示されるまで、一度に1つの子コンポーネントから削除します。それが原因のコンポーネントです。
// Temporary! Remove after finding the bug.
<body suppressHydrationWarning>
デバッグ後に削除
<body>のsuppressHydrationWarningは、グローバルにすべてのハイドレーション警告を抑制します。これはデバッグツールにすぎません。永続的な修正として本番環境にデプロイしないでください。
ステップ5:grepで一般的なパターンを検索する
プロジェクトのルートでこれらを実行して、最も可能性の高い原因を見つけます。
# Find direct localStorage / sessionStorage reads in render paths
grep -rn "localStorage\|sessionStorage" src/ --include="*.tsx" --include="*.ts" | grep -v "useEffect"
# Find components using window without a guard
grep -rn "window\." src/ --include="*.tsx" | grep -v "typeof window"
# Find suppressHydrationWarning usage (should be rare)
grep -rn "suppressHydrationWarning" src/ --include="*.tsx"
# Find dynamic imports without ssr:false on heavy chart/map components
grep -rn "import dynamic" src/ --include="*.tsx"
最初の2つのgrepの結果はそれぞれ、発生する可能性のあるハイドレーションエラーです。
ブラウザ拡張機能とサードパーティDOMスクリプトのハイドレーションエラーを修正する
これは、バグがあなたのコードにはまったくないため、デバッグが最も難しいカテゴリです。jQueryプラグイン、レガシーな分析スクリプト、埋め込みウィジェットなどのライブラリは、DOMを直接操作することがあります。属性を追加したり、ノードをラップしたり、要素を挿入したりします。
Reactのハイドレーションは、DOMがサーバーが生成したものと完全に一致することを期待しています。ハイドレーションが完了する前に外部のDOM変更があると、不一致が発生します。
一般的な原因:
- Google翻訳ウィジェット: DOM内のすべてのテキストノードを書き換える
- Hotjar / FullStory: iframeまたは属性オーバーレイを挿入する
- レガシーなjQueryプラグイン: マウント時に
.addClass()または.wrap()を呼び出す - ブラウザの翻訳機能: Google翻訳ウィジェットと同じ
修正方法: これらのスクリプトを、strategy="afterInteractive"とともにnext/scriptを使用してハイドレーション後に読み込みます。
import Script from 'next/script'
export default function Layout({ children }) {
return (
<>
{children}
{/* Load third-party DOM scripts only after React has hydrated */}
<Script
src="https://example.com/legacy-widget.js"
strategy="afterInteractive"
/>
</>
)
}
特にGoogle翻訳の場合、唯一の確実な修正方法は、メタタグを使用してブラウザの組み込み翻訳プロンプトを無効にすることです。Google翻訳は、Reactのハイドレーションと根本的に互換性のない方法でテキストノードを書き換えます。
<!-- In your <head>: disables browser translate prompt -->
<meta name="google" content="notranslate" />
まとめチェックリスト
| 間違い | 失敗する理由 | 安全なパターン |
|---|---|---|
| window / localstorage | 失敗: サーバーで未定義 | useEffectで初期化された状態を使用する |
| new Date() / Math.random() | 失敗: 実行間で値が異なる | マウントされた状態を介してレンダリングを遅延させる |
| <div> inside <p> | 失敗: ブラウザが無効なHTMLを書き換える | タグ構造を意味的かつ有効に保つ |
| Third-party DOM scripts | 失敗: ハイドレーション前にDOMを変更する | strategy="afterInteractive"を使用する |
Suspense境界とストリーミングSSRにおけるReactエラー #423 の解決
React 18では、Suspenseを介したストリーミングサーバーサイドレンダリングが導入されました。これにより、Time to First Byteが劇的に改善されましたが、ストリーミングチャンク中にハイドレーション境界が競合すると、縮小されたReactエラー #423 および #425 が発生します。
コンテンツを<Suspense>でラップすると、Reactはまずフォールバック(スピナーなど)をブラウザにストリーミングし、準備ができたときに解決されたコンテンツをストリーミングします。ハイドレーションの不一致は、クライアントがレンダリング中にストリーミングされたコンポーネントを異なるローカル状態と調整しようとするときに発生します。
// ❌ This can cause subtle hydration mismatches with streaming SSR
export default function Page() {
return (
<Suspense fallback={<Spinner />}>
<UserDashboard /> {/* Fetches data async */}
</Suspense>
);
}
// Inside UserDashboard: DON'T do this
function UserDashboard() {
const [theme] = useState(localStorage.getItem('theme')); // ❌ localStorage in SSR!
// ...
}
Suspense + ストリーミングの安全なパターン:
// ✅ Correct: keep client-only state inside useEffect, not initial state
function UserDashboard() {
const [theme, setTheme] = useState<string | null>(null); // null = server-safe default
useEffect(() => {
setTheme(localStorage.getItem('theme') ?? 'light');
}, []);
return <div data-theme={theme ?? 'light'}>...</div>;
}
React 18 ストリーミングルール
Suspense境界内のブラウザAPIを読み取るコンポーネントは、useEffect + マウントされた状態パターンを使用する必要があります。フォールバックレンダリングはサーバーで実行され、解決されたレンダリングはuseEffectが起動する前に最初のクライアントレンダリングと完全に一致する必要があります。
App Routerにおける「use client」シリアライゼーション境界の不一致の修正
App Routerのサーバーコンポーネントモデルは、Pages Routerには存在しなかった新しいカテゴリのハイドレーションエラーを導入します。その核心的なルールは次のとおりです。
サーバーコンポーネントは、非シリアライズ可能なプロップをクライアントコンポーネントに渡すことはできません。
関数、クラスインスタンス、Dateオブジェクト、またはundefinedをサーバー→クライアントコンポーネント境界を介して渡すと、Next.jsはビルド時にエラーをスローするか、実行時にサイレントなハイドレーションの不一致を引き起こします。
// ❌ This silently breaks hydration: Date is not serializable over the wire
// app/page.tsx (Server Component)
import ClientWidget from './ClientWidget'; // has 'use client'
export default function Page() {
const timestamp = new Date(); // Date object: NOT serializable!
return <ClientWidget createdAt={timestamp} />;
}
// ✅ Serialize to ISO string first: strings are always safe to pass
export default function Page() {
const timestamp = new Date().toISOString(); // plain string ✅
return <ClientWidget createdAt={timestamp} />;
}
// In ClientWidget: reconstruct on the client
'use client'
export function ClientWidget({ createdAt }: { createdAt: string }) {
const date = new Date(createdAt); // safe to construct on client
return <div>{date.toLocaleDateString()}</div>;
}
境界を破る一般的な非シリアライズ可能な型:
| 型 | エラー | 修正 |
|---|---|---|
Date | サイレントハイドレーションの不一致 | .toISOString() 文字列を渡す |
| 関数 | ビルドエラー | 関数をクライアントコンポーネントに移動する |
undefined | プロップがnullになる | 明示的なnull デフォルトを使用する |
| クラスインスタンス | サイレント不一致 | 代わりにプレーンオブジェクト{}を渡す |
BigInt | シリアライゼーションエラー | String(bigint)に変換する |
大規模なNext.jsアプリケーションでサーバーとクライアントの境界を分離する方法の詳細については、Next.js App Routerのフォルダー構造ガイドとNext.js App Routerのキャッシュ戦略ガイドを参照してください。
本番環境にデプロイする前にハイドレーションエラーをテストする方法
最高のハイドレーションエラーは、午前3時のSentryアラートではなく、ローカルで捕捉できるものです。ここでは、デプロイ前に4つの悪質なパターンをすべて捕捉するテスト戦略を紹介します。
1. React Strict Modeを有効にする(Next.jsではすでに有効)
Next.jsはデフォルトで<React.StrictMode>を有効にしています。開発環境では、これは意図的にコンポーネントを2回レンダリングして、副作用やハイドレーションの問題を表面化させます。開発中にコンポーネントが点滅したり、2回レンダリングされたりする場合は、Strict Modeが機能している証拠です。無効にしないでください。
2. マウントガードユニットテストを作成する
React Testing Library with user-event v14を使用して、コンポーネントがハイドレーション前にサーバーセーフなデフォルトをレンダリングすることを確認します。
import { render, screen } from '@testing-library/react'
import CurrentTime from './CurrentTime'
test('renders server-safe placeholder before mount', () => {
render(<CurrentTime />)
// On first render (simulating SSR), should show placeholder
expect(screen.getByText('Loading time...')).toBeInTheDocument()
})
3. suppressHydrationWarningの過剰使用をチェックする
すべてのPRの前にこれをgrepして、忘れている可能性のある抑制された警告を見つけます。
grep -r "suppressHydrationWarning" src/ --include="*.tsx"
ヒットした各項目には、その理由を説明するコメントが必要です。コメントがない場合は、隠れたバグです。
4. Next.js --turbopack開発モードを使用する
Turbopack (Next.js 14+) は、Webpackよりも正確なハイドレーションエラーメッセージを提供します。これには、サーバーとクライアントのHTML間の正確な差分が含まれます。これを有効にするには:
next dev --turbopack
次のステップ
ハイドレーションエラーを回避する方法を理解したところで、コンポーネントツリーのアーキテクチャを監査しましょう。大規模なアプリケーションでは、クリーンなNext.js App Routerフォルダー構造に従うことで、サーバーとクライアントのコンポーネント境界を厳密に分離するのに役立ちます。データ駆動型のインタラクティブフォームを構築している場合は、ハイドレーションのちらつきなしで変更を処理するためのReact 19 Server Actionsと楽観的更新に関するガイド、またはNext.js App RouterとPrismaを使用して直接データベースクエリを接続する方法について学習してください。
よくある質問
Next.jsのハイドレーションエラーは、Node.jsサーバーから送信されたプリレンダリングされたHTMLが、Reactがクライアントサイドの初期レンダリングパスで計算したものと一致しない場合に発生します。Reactがテキストノード、HTMLタグ階層、または属性の不一致を検出すると、サーバーDOMを破棄し、エラー #418 をスローし、強制的にクライアントサイドの再描画を行い、目に見えるUIのちらつきを引き起こします。
このエラーは、new Date().toLocaleTimeString()、Math.random()などの動的な値、またはブラウザに依存するグローバル変数(window、localStorage)が、サーバーのプリレンダリングとクライアントのマウントの間で異なる場合に発生します。クライアントマウントでuseEffectが完了するまで動的な値を遅延させるか、分離されたテキスト要素でsuppressHydrationWarningを利用することで解決します。
縮小されたReactエラー #423 は、ストリーミングSSR中にReact 18 Suspense境界内でハイドレーションが失敗した場合に発生します。ストリーミングされたサーバーチャンクがアクティブなクライアント状態またはサードパーティのDOM変更と競合する状態で解決されると、Reactはフォールバックを再レンダリングします。Suspense境界内のコンポーネントがサーバーセーフなデフォルトで状態を初期化し、クライアント専用のアクセスをuseEffectに遅延させることで修正します。
HTML5の仕様では、<p>タグ内に<div>、<p>、<ul>、<table>などのブロックレベル要素をネストすることを禁止しています。ブラウザのHTMLパーサーが無効なネストを検出すると、実際のDOMでタグを自動的に再構築して早期に閉じ、React SSRが期待するツリー構造を破壊します。
next-themesはハイドレーション前にlocalStorageから色の設定を読み取るため、app/layout.tsxのルートの<html>要素にsuppressHydrationWarningを追加します。これにより、Reactはトップレベルコンテナの属性の違いを無視し、子コンポーネントのハイドレーションの安全性を損なうことはありません。
コンポーネントの初期レンダリング中にlocalStorageを読み取らないでください。サーバーセーフなデフォルト(nullまたはfalse)で状態を初期化し、その後、ハイドレーションが完了した後にブラウザでのみ実行されるuseEffect内で状態を更新します。
suppressHydrationWarningは、クライアントの起動時にすぐに変更されることが保証されている単純なテキストノードや属性(フォーマットされたタイムスタンプ、<html>のテーマクラス、ユーザーロケール文字列など)に限定して使用してください。<body>や大規模なインタラクティブコンポーネントツリーに包括的な修正として使用しないでください。
インタラクティブハイドレーションデバッガーと関連アーキテクチャガイド
エラーログまたはコンポーネントコードを無料のNext.js Hydration Error Matcher & Fixer Toolに貼り付けて、リアルタイムの根本原因分析とコピー&ペースト可能なコードソリューションを入手してください。
プロダクションエンジニア向け推奨読書資料:
Free In-Browser Developer Tools
Clean AI CLI logs, build cron expressions, decode JWTs, and calculate chmod permissions offline.
Related Articles

React 19: サーバーアクションとオプティミスティックUI(ゼロラグ体験)
useOptimisticとサーバーアクションをマスターし、ネットワーク障害時の自動ロールバックを実現。本番用のコード例、トランジションパターン、シーケンス図で解説します。
Read more
Next.js App Router動的再検証ガイド
Next.js App Routerのキャッシュアーキテクチャ、fetchリクエストのメモ化、revalidateTagによるデータキャッシュ無効化、オンデマンドISR再検証を習得しましょう。
Read more
カスタムReactHookのパフォーマンス最適化パターン
安定したrefのキャッシュ、リスナーのバッチ処理、メモ化されたセレクター、プロファイラー技術を活用して、カスタムReactHookのパフォーマンス最適化をマスターしましょう。
Read more