🥟

React18+Next.jsにおけるHydration failedについて

2022/05/08に公開

はじめに

React18+Next.jsで開発をしていたところ初見のエラーに遭遇しました。
エラーの原因が長らくわかりませんでしたが、なんとか解決できたのでその知見を紹介したいと思います。

エラー内容

エラー内容は

Error: Hydration failed because the initial UI does not match what was rendered on the server.

「初期UIがサーバでレンダリングされたものと一致しないためHydrationに失敗しました。」という意味になります。ここでHydrationについて考えます。

Hydrationとは

Next.jsでは生成されたHTMLが、そのページに必要な最小限のJavaScriptのコードと関連付けられることで、ブラウザがページを読み込んだ際に、そのJavaScriptが実行されページが操作可能(インタラクティブ)になります。この過程をHydrationといいます。
https://nextjs.org/learn/basics/data-fetching/pre-rendering

エラーの原因

Hydrationについて理解したところで、今回のエラー原因について考えたいと思います。
今回のエラーが生じたコードが以下となります。(エラーの本質部分だけ)

バージョン

  • react 18.1.0
  • next 12.1.6
import { NextPage } from "next";
import React from "react";

const TopPage: NextPage = () => {
  return (
    <>
      <p>
        <div>hello</div>
      </p>
    </>
  );
};

export default TopPage;

HTMLに詳しい人なら当たり前かもしれませんが,pタグのなかにdivタグがあることが問題です。
以下にもあるようにpタグ内はimgタグbrタグといった記述コンテンツしか使用できません。
https://developer.mozilla.org/ja/docs/Web/HTML/Element/p

pタグを用いた理由

pタグを用いてしまった理由としてはパフォーマンスを意識しすぎたためです。
以前以下のような記事を読み、divタグの多様がパフォーマンスの低下につながることを学んだため、divタグpタグに変更する方針で開発していました。
しかしpタグの仕様を深く理解していなかったため、divタグとの互換性がなく誤用してしまいました。
https://zenn.dev/uhyo/articles/usememo-time-cost

React17との比較

私は以前からdivタグの代わりにpタグを用いていました。
しかしそのときは、Hydration failedは発生せず開発できていました。
その時のReactのバージョンがReact17だったのでReact17React18で比較してみます。

React17

バージョン

  • react 17.0.2
  • next 12.0.9
import { NextPage } from "next";
import React from "react";

const TopPage: NextPage = () => {
  return (
    <>
      <p>
        <div>hello</div>
      </p>
    </>
  );
};

export default TopPage;

先程のReact18のときとコード内容は同じにしてコンソールパネルで表示に違いがでるか比較しました。以下がReact17における結果です。

React18

React18における結果が以下となります。





このことからもわかるように、pタグの誤用はReact17React18のどちらも警告止まりであることがわかります。しかしReact18以降では警告に加えHydrationに関するエラーが出現するようになりました。

解決策

現在の所感では、Hydration faliedのエラーにもあるように不正なHTMLを用いることで、サーバで生成されたHTMLとクライアントで生成されたHTMLに違いがでてしまっているのかなというところです(なぜ違いが出てしまっているのかはわからない)。したがってHydration failedが生じたときはHTMLのタグの使い方に間違いがないかを確認することが先決です。それでも解決しない場合はReact17にダウングレードするのがいいかもしれません。

参考

Discussion