Next.jsなどのフレームワークを使用していると
「window is not defined」というエラーに出会ったり、
「Hydration failed because the server rendered HTML didn’t match the client」というエラーに出会うことが多いので、勉強も兼ねて記事にしてみました。
SSRとは
SSRは、サーバー上でHTMLを事前に生成し、クライアント(ブラウザ)に送信する仕組みです。
ReactなどのSPAでは、通常はクライアント側でJavascriptが実行されて初めて画面が描画されますが、SSRを使うとサーバーであらかじめHTMLが生成されるため、ブラウザが受け取った時点でページの構造が出来上がっており、SEO対策や表示速度の向上に繋がります。
ブラウザAPIとは
クライアント(ブラウザ)で動作するための特別なオブジェクトです。
これらはブラウザが持つ機能で、例えばwindow.innerWidthで画面幅を取得したり、document.getElementByIdでDOM要素を操作することができます。
なぜ両者の相性が悪いのか
SSRが動作するサーバー側は、Node.js環境で実行されているため、ブラウザの機能を持っていません。
そのため、SSR中にwindowやdocumentを参照すると、それらは存在しないためエラーになります。
Hidrationエラーについて
SSRによってサーバーから送られたHTMLは、あくまで静的なHTMLです。
これに対して、クライアント側でReactを起動し、イベントのバインドや状態管理を有効化するプロセスをハイドレーションと呼びます。
この時、サーバーで生成されたHTMLとクライアント側でReactが再構築するDOMが一致していない場合、
「Hydration failed because the server rendered HTML didn’t match the client.」というエラーや警告が発生します。
これは、サーバーとクライアントで描画結果が異なるときに起きる現象です。
解決方法
①クライアント専用の処理はuseEffect内に書く
SSR中にブラウザAPIを参照してしまうと、サーバー側にはそれらが存在しないためエラーになります。
そのため、クライアントでのみ動作させたい処理はuseEffect内に記述するのが基本的な対処法
useEffectはコンポーネントがマウントされたあとに実行されるため、サーバーでは実行されずにクライアント側でのみ実行されます。
②条件分岐でサーバーかクライアントかを判定する
もうひとつの解決方法は、サーバーかクライアントかを明示的に判定する方法です。
typeof window !== 'undefined'を使って、ブラウザ環境かどうかをチェックすることができます。
export default function Example() {
const width = typeof window !== 'undefined' ? window.innerWidth : 0;
return <p>画面幅は {width} px です。</p>;
}
この場合、SSR中は0を返し、クライアント側ではwindow.innerWidth を返します。