2025-01-08
ある日、本番環境でハイドレーションエラーが発生していることに気づきました。 そのハイドレーションエラーの原因が中々に厄介だったので、備忘録として残しておきます。
先に原因を書くと、Next.jsのSSRとCSRのタイムゾーンが異なることでした。 Vercelを使用している場合Serverless FunctionがUTCで動作しているのに対して、クライアント側のJavaScriptはブラウザのタイムゾーンで動作しているため、タイムゾーンが違います。 そのため、SSRで生成されたHTMLと、CSRで生成されたHTMLのタイムゾーンが異なることでハイドレーションエラーが発生していました。
Uncaught Error: Minified React error #425; visit https://reactjs.org/docs/error-decoder.html?invariant=425 for the full message or use the non-minified dev environment for full errors and additional helpful warnings.
Uncaught Error: Minified React error #418; visit https://reactjs.org/docs/error-decoder.html?invariant=418 for the full message or use the non-minified dev environment for full errors and additional helpful warnings.
Uncaught TypeError: undefined is not a function
という3つのエラーが発生していました。 上の2つのエラーはエラー文に含まれていたURLにアクセスすると、ハイドレーションエラーが発生したということが書かれていました。
Next.jsでServer Side Rendering(SSR)で生成されたHTMLをクライアント側のJavaScriptによってイベントリスナが登録されたり、インタラクティブな要素が追加されることを指します。
色々調べてはみたのですが、完璧に解決する方法は見つかりませんでした。 紹介する方法はどちらもデメリットがあるので、状況に応じて使い分けることをお勧めします。
今回自分が採用した方法です。 クライアントサイドでタイムゾーンを固定することで、SSRとCSRで生成されるHTMLのタイムゾーンを合わせることができます。 こちらの方法のデメリットとしては、ユーザーのタイムゾーンに合わせた日付を表示することができなくなることです。
自分が関わっているプロジェクトではdata-fns
を使用しているので、data-fns
のformat
関数を使用してタイムゾーンを固定しました。
またラップしたformat
関数のみを使用することにしたかったので、eslintでdate-fns
のformat
関数を使用しようとするとエラーを出すようにしました。
こちらはSSRで生成されたHTMLには日付を含めず、CSRで日付をレンダリングする方法です。 この方法のデメリットとしては作成したコンポーネントを毎回ちゃんと使用させることが難しく、漏れが発生した場合にハイドレーションエラーが発生する可能性があることです。
参考: https://francoisbest.com/posts/2023/displaying-local-times-in-nextjs
ハイドレーションエラーの原因を特定するのにとても時間がかかりました... こんなところに落とし穴があるとはという感じでした。 今回紹介した解決方法はどちらもデメリットがあるため、最適な解決方法を見つけることができたらまた追記します。