2025-01-08

Next.jsでSSRとCSRのタイムゾーンが違うことでハイドレーションエラーが発生した

ある日、本番環境でハイドレーションエラーが発生していることに気づきました。 そのハイドレーションエラーの原因が中々に厄介だったので、備忘録として残しておきます。

原因

先に原因を書くと、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-fnsformat関数を使用してタイムゾーンを固定しました。

またラップしたformat関数のみを使用することにしたかったので、eslintでdate-fnsformat関数を使用しようとするとエラーを出すようにしました。

CSRでのみ日付はレンダリングするようにする

こちらはSSRで生成されたHTMLには日付を含めず、CSRで日付をレンダリングする方法です。 この方法のデメリットとしては作成したコンポーネントを毎回ちゃんと使用させることが難しく、漏れが発生した場合にハイドレーションエラーが発生する可能性があることです。

参考: https://francoisbest.com/posts/2023/displaying-local-times-in-nextjs

まとめ

ハイドレーションエラーの原因を特定するのにとても時間がかかりました... こんなところに落とし穴があるとはという感じでした。 今回紹介した解決方法はどちらもデメリットがあるため、最適な解決方法を見つけることができたらまた追記します。

Thanks for the visit Nozo Blog