本文へ移動
verified onnext@16.3.3react@19
最終検証日 2026-08-27
Next.js道場

Hydration failed の直し方

実際に出るエラー

Hydration failed because the server rendered text didn't match the client.
As a result this tree will be regenerated on the client.

This can happen if a SSR-ed Client Component used:
- A server/client branch `if (typeof window !== 'undefined')`.
- Variable input such as `Date.now()` or `Math.random()` which changes each time it's called.
- Date formatting in a user's locale which doesn't match the server.
- External changing data without sending a snapshot of it along with the HTML.
- Invalid HTML tag nesting.

サーバーで描いた HTML と、ブラウザが最初に描いた内容が食い違ったときに出るエラーです。

#何が起きているか

Client Component は、最初の1回だけサーバーでも実行されます。その結果が HTML として送られ、ブラウザ側で React が同じものをもう一度描いて、両者を突き合わせます。この突き合わせが hydration です。

このとき中身が食い違うと、React は「サーバーが送ってきた HTML は信用できない」と判断し、その部分を作り直します。そのときに出るのがこのエラーです。

サーバー  <p>2026-08-28T01:40:31.478Z</p>   ← HTML として送られる
ブラウザ  <p>2026-08-28T01:40:31.676Z</p>   ← 描き直したら違った
                                  ↑ 食い違い

#致命的ではない、が放置してはいけない

Next.js の開発オーバーレイでは Recoverable Error(回復可能)と表示されます。実際、React はその部分を作り直すのでページは動きます。

ただし、

  • 作り直しのぶん、表示が一瞬ちらつく
  • サーバーが作った HTML が捨てられるので、最初の表示が速いという利点が消える
  • 食い違いの原因が「ブラウザ限定の値を描画時に読んでいる」なら、別の不具合の予兆でもある

ので、直すべきです。

#原因はほぼこの5つ

React 自身がエラー文の中で候補を挙げてくれます。実際に多いのは上の3つです。

1. typeof window で分岐している

'use client'

export function Stored() {
  const v = typeof window === 'undefined' ? 'server' : 'browser'
  return <p>{v}</p>   // サーバーとブラウザで必ず違う
}

window is not defined を消すためにこう書くと、エラーが hydration の不一致に置き換わるだけです。

2. 時刻や乱数を描画に使っている

return <p>{new Date().toISOString()}</p>   // 呼ぶたびに変わる
return <p>{Math.random()}</p>

サーバーで描いた瞬間と、ブラウザで描いた瞬間は必ずずれます。

3. 日付や数値の書式が環境で変わる

toLocaleString() は、サーバーとブラウザでロケールやタイムゾーンが違うと別の文字列になります。

4. HTML の入れ子が正しくない

<p> の中に <div> を入れるなど。ブラウザが勝手に構造を直すので、React が描いたものと合わなくなります。

5. ブラウザ拡張が HTML を書き換えている

自分のコードに問題が無いのにこのエラーが出る場合、これを疑います。シークレットウィンドウで開くと再現しないなら、拡張が原因です。

#最短の確認手順

開発モードで開いてください。オーバーレイが食い違った箇所を差分で見せてくれます

+ 2026-08-28T01:40:31.676Z    ← Client(ブラウザが描いたもの)
- 2026-08-28T01:40:31.478Z    ← Server(HTML として届いたもの)

components/Clock.tsx (5:10) @ Clock

行番号まで出るので、原因の特定はここでほぼ終わります。

**本番ビルドでは Minified React error #418 としか出ません。**必ず開発モードで再現させてください。

#直し方

推奨:最初の描画では出さず、useEffect の後に出す

'use client'

import { useEffect, useState } from 'react'

export function Clock() {
  const [now, setNow] = useState<string | null>(null)

  useEffect(() => {
    setNow(new Date().toISOString())
  }, [])

  return <p>{now ?? '—'}</p>
}

サーバーもブラウザも最初は null を描くので、食い違いません。値が入るのは hydration の後です。

値をサーバーから渡す

時刻を「ページを作った時刻」として見せたいなら、Server Component で決めて props で渡します。両方が同じ値を描くので一致します。

// Server Component
const now = new Date().toISOString()
return <Clock now={now} />

書式をサーバーと揃える

toLocaleString() を使うなら、ロケールとタイムゾーンを明示します。

new Intl.DateTimeFormat('ja-JP', { timeZone: 'Asia/Tokyo' }).format(d)

どうしても違ってよい場合

1要素だけなら suppressHydrationWarning で警告を止められます。ただし警告を消すだけで、作り直しは起きます。乱用しないでください。

#やってはいけない直し方

const [mounted, setMounted] = useState(false)
useEffect(() => setMounted(true), [])
if (!mounted) return null

「マウントするまで何も描かない」という形です。エラーは消えますが、

  • そのコンポーネントの初期表示が丸ごと無くなる
  • サーバーで HTML を作った意味が消える
  • 表示が遅れて見える

ので、部分的な回避にしか使えません。食い違う値だけを後から入れるほうが素直です。

#なぜ開発中に気づきにくいのか

気づけます。むしろ開発モードのほうが親切です。問題は逆で、本番でしか出ないケースがあることです。

  • 利用者のタイムゾーンやロケールが開発環境と違う
  • 利用者のブラウザ拡張が HTML を書き換える
  • 本番でだけ通るデータの経路がある

本番で #418 を見たら、まず同じ条件を開発モードで再現させてください。

#再発防止

  • 描画の中で「呼ぶたびに変わる値」を使わない
  • typeof window で表示を分岐させない
  • ロケールに依存する書式は、必ずロケールとタイムゾーンを明示する

#関連するエラー

window をサーバー側で参照した場合は、hydration の前に落ちます。

ReferenceError: window is not defined

typeof window での分岐は、このエラーを hydration の不一致に置き換えているだけ、という関係になります。

理解できたか試す

では、このコードはどうなりますか。

use client が付いています。ブラウザで開くと何が起きますか。

この問題を解くこのテーマの問題 3