FrontCreation Logo

TECH BLOG

Next.jsでHydration Errorを解消する方法

2025.08.10

Next.js を触ってて、こんなエラーに出会いました。

A tree hydrated but some attributes of the server rendered HTML didn't match the client properties. 
This won't be patched up. 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.

今回の記事ではなぜこういうエラーが起きるのか、そしてどう解決すれば良いかを解説していきたいと思います。

Hydration Errorとは

SSRで描画したHTMLに対して、クライアント側のReactが「再活性化(Rehydrate)」するときに、両者のDOMや属性が一致しないと発生するエラーです。例えば、サーバーのHTMLにはdata-foo="a"があるのに、クライアントが描こうとしている仮想DOMはdata-foo="b"だった、などのケースです。

一致しない原因はアプリ側の非決定的なレンダリングだけではありません。ブラウザ拡張や外部スクリプトがDOMを書き換えると、アプリの責務外でも簡単に不一致が起きます。

典型的な発生要因

  • 非決定的な値の使用(現在時刻、Math.random()、リクエストごとのID など)
  • Server ComponentとClient Componentの境界ミス(ブラウザAPIをServer Componentで参照 など)
  • 初期レンダリング時のデータ差(CSR側だけでフェッチして内容が変わる)
  • ロケール/タイムゾーン差による表記ゆれ(日時・数値フォーマット)
  • 外部要因のDOM改変(広告タグ、A/Bテストツール、ブラウザ拡張 など)

小さな例として、初期描画で時刻を直接出すとズレやすくなります。

// 悪い例(初期HTMLとクライアントでズレる可能性)
export default function Clock() {
  return <span>{new Date().toLocaleTimeString()}</span>
}
 
// 改善例(初期はサーバーと一致させ、クライアントで更新)
"use client";
import { useEffect, useState } from "react";
export default function ClockFixed() {
  const [time, setTime] = useState<string>("");
  useEffect(() => {
    setTime(new Date().toLocaleTimeString());
  }, []);
  return <span suppressHydrationWarning>{time}</span>;
}

suppressHydrationWarningは不一致を既知として黙らせるための最終手段です。根本原因を直せるなら、まずは直すのが原則です。

今回の結論:Chrome拡張「colozilla」が原因だった

新規のNext.jsアプリ(create-next-app直後)で、特にアプリ側のロジックを追加していないのにHydration Errorが出て困っていました。致命的ではなく×で閉じれば開発は続けられるのですが、理由が分からないのが不安でした。

<html lang="ja">
  <body
    className="geist_e531dabc-module__QGiZLq__variable geist_mono_68a01160-module__YLcDdW__variabl..."
-   cz-shortcut-listen="true"
>
  32 |   return (
  33 |     <html lang="ja">
> 34 |       <body
     |       ^
  35 |         className={`${geistSans.variable} ${geistMono.variable} ${inter.variable} ${noto.variable} antialiased`}
  36 |       >
  37 |         <RouteLoader />

cz-shortcut-listen...なんだろう。調べた結果、ブラウザに入れていたカラー取得用拡張「colozilla」が原因でした。 ページ上の色をスポイトで取り、カラーコードを表示するタイプの拡張です。拡張を無効化(または削除)するとエラーは発生しなくなりました。

拡張がDOMへ属性やノード、スタイルを注入すると、サーバーが返した初期HTMLとクライアントの仮想DOMが一致しなくなります。そのため、アプリのコードに手を入れていなくてもHydration Errorが起こり得ます。

再現と確認手順

  1. npx create-next-app@latest でプロジェクトを作成
  2. npm run dev で起動しトップページへアクセス
  3. Chrome拡張「colozilla」を有効化した状態でページをリロード
  4. エラーオーバーレイにHydration Errorが表示される
  5. 同拡張を無効化してリロードすると、エラーが消えることを確認

切り分けのポイント(拡張・環境起因を疑う)

  • シークレットウィンドウ(拡張無効)で再現するかを確認
  • 別プロファイルや別ブラウザ(Edge/Safari/Firefox)で比較
  • 拡張を一括で無効 → 問題なしになったら、二分探索で原因拡張を特定
  • 外部スクリプト(タグマネージャ、A/Bテスト、広告)を外して検証

Next.js側での一般的な対処

  • 非決定的な値の初期表示を避ける(初期はプレースホルダ、useEffectで更新)
  • ブラウザAPI使用箇所は確実にClient Component化("use client"
  • サーバーとクライアントの初期データを揃える(同一ソース・同一整形)
  • どうしても差分が出る箇所は局所的にsuppressHydrationWarningを使用
  • CI/E2Eで主要ブラウザ・拡張無効プロファイルを含めた検証を行う

開発時のミニチェックリスト

  • まず拡張をオフにして再現するか?
  • 非決定的な表示(時間・乱数・UA依存)は初期描画に含めていないか?
  • Server/Clientの境界(appディレクトリ、use client)は正しいか?
  • 依存ライブラリが初期DOMを変えていないか?

まとめ

  • Hydration Errorは「サーバーの初期HTML」と「クライアントが描くDOM」の不一致で起きる
  • 原因はアプリのコードだけでなく、ブラウザ拡張や外部スクリプトもあり得る
  • 今回はChrome拡張「colozilla」のDOM介入が原因で、無効化すると解消した
  • 今後は拡張・環境起因も含めて切り分けること、非決定的な初期描画を避けることがポイント

参考サイト