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

Hydration とは何か

Hydration は、サーバーが送った HTML に、ブラウザ側の React を接続して操作できるようにする処理です。

#なぜ二度描くのか

サーバーが HTML を送るだけなら、画面はすぐ出ます。ただし、そのままではボタンを押しても何も起きません。イベントを繋ぐ JavaScript が動いていないからです。

そこで、ブラウザ側でも同じ内容をもう一度描いて、送られてきた HTML に React を接続します。これが hydration です。

1. サーバーで描く    ──▶ HTML  「見える」だけの状態
2. JavaScript が届く
3. ブラウザで描き直す ──▶ 突き合わせて接続  「押せる」状態になる

乾いた HTML に水を差して動くようにする、という比喩から来た名前です。

#対象は Client Component だけ

ここを取り違えると、原因の見当がずれます。

サーバーで実行ブラウザで実行hydration の対象
Server Componentするしない対象外
Client Componentする(最初の1回)する対象

Client Component は「ブラウザだけで動く」ものではありません。「ブラウザにも送られる」ものです。最初の1回はサーバーでも実行されます。

だから、'use client' を書いたファイルの中で window を直接読むと、その1回目で落ちます。

#食い違うと何が起きるか

サーバーが描いたものと、ブラウザが描いたものが違うと、React は「送られてきた HTML は当てにならない」と判断して、その部分を作り直します。

サーバー  <p>2026-08-28T01:40:31.478Z</p>
ブラウザ  <p>2026-08-28T01:40:31.676Z</p>
                              ↑ ここが違う → 作り直し

Next.js の開発オーバーレイは、これを差分で見せてくれます。+ がブラウザ側、- がサーバー側です。

ページは動き続けます(開発モードでは Recoverable Error と表示されます)。ただし、

  • 作り直しのぶん、表示が一瞬ちらつく
  • サーバーで HTML を作った意味が薄れる

ので、直すべき状態です。

#食い違いを生まないための原則

最初の描画では、サーバーとブラウザで必ず同じものを描く。

これだけです。違ってよいのは hydration が終わったあと、つまり 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>           // 最初は両方 '—'
}

#食い違いを生みやすいもの

やっていることなぜ食い違うか
Date.now() new Date() を描画で使う呼ぶ瞬間が違う
Math.random()毎回変わる
typeof window で分岐サーバーとブラウザで必ず別の枝を通る
toLocaleString() をロケール指定なしで使う環境で書式が変わる
localStorage を初期値に使うサーバーには存在しない
<p> の中に <div> を置くブラウザが構造を勝手に直す

最後の1つは見落としやすいところです。HTML として不正な入れ子は、ブラウザが黙って修正するので、React が描いた木と合わなくなります。

#typeof window は解決策ではない

window is not defined を消すために、こう書きたくなります。

const w = typeof window === 'undefined' ? 0 : window.innerWidth

参照エラーは消えます。ですが、サーバーは 0、ブラウザは実際の値を描くので、今度は hydration の不一致になります。 エラーが別の場所へ移っただけです。

React 自身も、エラー文の原因候補の筆頭にこの書き方を挙げています。

#サーバーから値を渡すという手

「ページを作った時刻」を見せたいだけなら、Server Component で決めて渡せば一致します。

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

サーバーもブラウザも同じ文字列を描くので、食い違いません。「ブラウザで計算する必要が本当にあるか」を先に考えると、多くの場合これで済みます。

#本番と開発で見え方が違う

何が出るか
開発モード全文のエラー、原因の候補、差分、該当行
本番ビルドMinified React error #418 だけ

本番で #418 を見たら、同じ条件を開発モードで再現させてから原因を探してください。本番のログを睨んでいても、行番号は出てきません。

#自分のコードに心当たりが無いとき

ブラウザ拡張が HTML を書き換えていることがあります。シークレットウィンドウで再現しないなら、それが原因です。この場合はコード側では直せません。

理解できたか試す

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

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

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