Reactのtree hydratedエラーの解決方法

React Router v7を使っていたところ、次のようなエラーがブラウザのコンソールに表示されました。

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.
https://react.dev/link/hydration-mismatch

本記事では筆者が遭遇したケースでのtree hydratedの解決方法を紹介します。

hydration関係について知らない方は、みんちゃんさんによる記事Next.js で Hydration Error が起きる理由と解決方法をご覧ください。

状況

  • React Router v7でSSRをしている
  • 上記のリストに書かれているような処理はしてない
  • Firefoxのみで発生

コンソールをよく見てみる

よくコンソールを見ると、エラーの詳細としてdiffが出力されてます。

コンソールにdiffがあった
...
<Router basename="/" location={{pathname:"...", ...}} navigationType="POP" navigator={{...}}>
<DataRoutes routes={[...]} future={{...}} state={{...}}>
<RenderErrorBoundary location={{pathname:"...", ...}} revalidation="idle" component={<Layout>} error={undefined} ...>
<RenderedRoute match={{params:{}, ...}} routeContext={{...}}>
<Layout>
<html lang="ja">
<head>
<meta>
<meta>
<Meta>
<Links>
<style
dangerouslySetInnerHTML={{
__html: "\n/* /app/styles/reset.css */\n/* html\r\n-------------------------------------------..."
__html: "\n/* /app/styles/reset.css */\n/* html\n---------------------------------------------..."

上記では分かりやすく色をつけていますが、実際には全部同じ色で書かれています。そのため筆者は気づくまでに時間がかかりました。

原因

diffをよく見てみると、改行コードが\n\r\nで違っていました。

改行コードが変わってる
__html: "\n/* /app/styles/reset.css */\n/* html\r\n---..."
__html: "\n/* /app/styles/reset.css */\n/* html\n-----..."

どうやら改行コードがCRLFのものをFirefoxで読み込んだときにLFに変換されたことで「差が生じたよ」といわれているようです。

該当するreset.cssは他の開発者の方からいただいたCSSファイルをコピーしたものです。

解決法

まずは他にもCRLFのものがあるのではと思って確認しました。fileコマンドでCRLFかどうかが分かります。

Terminal window
file app/**/*.css | grep CRLF

ファイルごとにCRLFからLFへ置換しました。今回はawkを使いました。

Terminal window
awk '{ sub(/\r$/, ""); print }' app/styles/reset.css > app/styles/reset.css.lf \
&& mv app/styles/reset.css.lf app/styles/reset.css

以上、tree hydratedのエラーの解決方法でした。

なぜFirefoxのみで発生したのかについては分からなかったため、分かり次第追記します。