【Next.js】エラー対処法:Hydration Mismatch(ハイドレーションの不一致)とは何か?原因

nextjs-prograshi(プロぐらし)-kv Next-js
記事内に広告が含まれていることがあります。

Next.jsで開発を進めていると、「A tree hydrated but some attributes of the server rendered HTML didn’t match…」という長いエラーメッセージに遭遇した人も多いのではないでしょうか?

このエラーは「Hydration Mismatch(ハイドレーションの不一致)」と呼ばれ、初心者にとって最初の大きな壁になりがちです。画面は表示されているのに、なぜコンソールに赤文字が出るのか、そもそも「ハイドレート」とは何なのか?

本記事では、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.

It can also happen if the client has a browser extension installed which messes with the HTML before React loaded.


hydrateRoot – React
The library for web and native user interfaces
layout.tsx:26
エラーの意味

ツリーはハイドレートされましたが、サーバー側でレンダリングされた HTML の一部の属性がクライアント側のプロパティと一致しませんでした。これは修正されません。SSR されたクライアントコンポーネントで以下の処理が使用されている場合に発生する可能性があります。

  • サーバー/クライアント分岐 if (typeof window !== 'undefined')
  • 呼び出されるたびに変化する Date.now()Math.random() などの変数入力
  • ユーザーのロケールにおける日付フォーマットがサーバー側と一致しない
  • スナップショットを HTML と共に送信せずに外部からデータを変更している
  • 無効な HTML タグのネスト

また、React が読み込まれる前に HTML を混乱させるブラウザ拡張機能がクライアントにインストールされている場合にも発生する可能性があります。


ハイドレート(ハイドレーション)とは何か?

Next.jsでは、サーバー側でHTMLを作成してブラウザに送り、その後にブラウザ側でJavaScriptを動かしてボタンなどを操作可能にします。

この静的なHTMLを動的なページに変える工程をHydration(ハイドレーション)、あるいはハイドレートと呼びます。

「ハイドレーション(Hydration)」という言葉は、直訳すると「水分補給」という意味です。

Next.jsやReactの世界では、「乾いたHTMLに、JavaScriptという『水』を与えて、動ける状態にする」という比喩で使われます


ハイドレーションの厳しいルール

ハイドレーションには1つの厳しいルールがありあす。

ハイドレーションの厳しいルール

「サーバーで作ったHTML」と「ブラウザで最初に動かした結果」が、1ミリも狂わずに一致していなければならない

つまり、ブラウザ側でJavaScriptを動かした結果、中のコードが1か所でも変わったらエラーが発生するということです。


エラーの内容:Hydration Mismatch(ハイドレーションの不一致)

今回発生しているエラーはHydration Mismatch(ハイドレーションの不一致)です。

まさに、上記のハイドレーションのルールに一致せず、サーバー側で生成したHTMLとブラウザ側でJavaScriptを動かした後のコードで違いがあるということです。

エラーの内容を見ていくと、左側にマイナス「-」がついている行があります。

<html
    lang="en"
-   data-google-analytics-opt-out=""
  >

この -(マイナス)がついている部分は、サーバーには無かったのに、ブラウザで読み込んだら勝手に追加されていた内容を指しています

具体的には、Googleアナリティクスを無効化するブラウザの拡張機能(アドオン)などが、<html>タグに勝手に属性を書き加えてしまったことが原因である可能性が高いです。


対処法

まずはシークレットモードで確認

まずは、全体で発生しているエラーなのか、特定のブラウザで発生しているエラーなのか確認します。

シークレットモードで開いてconsoleを確認します

今回の場合、シークレットモードではハイドレーション不一致のエラーは発生しないので、コードのバグではなく、環境依存であることが分かります


★suppressHydrationWarning 属性を使う

今回のように、ブラウザ拡張機能が勝手にタグを書き換えてしまうのを無視したい場合は、layout.tsx の該当するタグに suppressHydrationWarning属性 を追加します。

    <html lang="ja" suppressHydrationWarning>
      <body
        className={`${geistSans.variable} ${geistMono.variable} antialiased`}
      >
        {children}
      </body>
    </html>
適用されるのは1階層下まで

suppressHydrationWarning属性は1レベル下の階層までしか効果がありません。

他にも同様のエラーがある場合は、該当するタグにsuppressHydrationWarningを設置する必要があります。


これでエラーが出なくなります。


useEffectを使う|クライアントでのみ実行するようにする

もし Date.now()window の有無によって表示を切り替えていることが原因の場合は、useEffect を使って「ブラウザに表示されてから実行する」ように修正します。

"use client";
import { useState, useEffect } from "react";

export default function MyComponent() {
  const [isClient, setIsClient] = useState(false);

  useEffect(() => {
    setIsClient(true);
  }, []);

  return (
    <div>
      {/* ブラウザでのみ表示されるので不一致が起きない */}
      {isClient ? "ブラウザで表示中" : "読み込み中..."}
    </div>
  );
}

クリーンアップ関数にブラウザ側でのみ表示する内容を記述します。


よくあるエラー原因

今回はブラウザのアドオンにより勝手にコードが追加されていたことが問題でした。

それ以外にもハイドレーションの不一致が発生することはあります。


不適切なHTML構造

p タグの中に div タグを入れるといった間違ったタグの記述をすると、ブラウザが自動修復して不一致が発生します。


時間の表示

new Date() を直接レンダリングしている場合、サーバーとクライアントで1秒ズレるだけでエラーになります。


ブラウザ固有の情報の利用

localStoragewindow.innerWidth などを useEffect の外で使っている場合も、不一致のエラーが発生します。

タイトルとURLをコピーしました