追記 (2022/04/19)
React18より、この記事で解説している内容はワーニングからエラーへ引き上げられました。
この記事で解説しているのは、React17の頃にワーニングレベルであったこの警告を「なぜ放置してはいけないか」ということについてです。
残念ながら 「どうやって解消すべきか」については触れていません ので、問題解決を急ぎたい方は、今すぐこの記事を閉じて他の記事を探してください。
結論
パフォーマンスに与える影響が非常に大きい。
「コンソールにワーニング出てるけど、レンダリングされる結果は問題ないからいいか」ぐらいの軽い気持ちで無視すると痛い目を見る
Warning解消前 | Warning解消後 | |
---|---|---|
1回目 | ||
2回目 | ||
ちなみに、このサンプルはJSON-LD(構造化マークアップデータ)の出力がサーバサイドとクライアントサイドに微妙な差異が生じていたときの例。コンテンツ量が多めのページで比較すると、差が顕著に出る。 |
ちなみに、パフォーマンスプロファイルで見ると、「before-hydrate」 + 「hydrating」 に100~300msの差が出ていた
SSG/SSR しているのに、CSRしているのと何も変わらない状態になる。
何が起こっているのか
ReactDOMServer と Hydrate
CSR な React アプリケーションは ReactDOM.render
を使って DOM をマウントしている。
React でアプリケーションを構築した人は、必ずと言ってよいほど見たことがあるだろう。
ReactDOM.render(App, document.getElementById('root'))
Next.js や Gatsby などの、 SSR/SSG を行うフローではこのマウントの方法が少し異なる。
上の画像の2番では、ReactDOMServer
によって、React のコードから HTML が生成される。
https://ja.reactjs.org/docs/react-dom-server.html
(このときの HTML は、useEffect
のような、マウントに対しての副作用的な処理が実行される前の HTML であることをイメージとして持っておくと良い。「マウント」はクライアント側でしか発生しない。)
その後、HTML と JS がクライアントに送信され、5番が処理される。
このとき、ReactDOM.render
ではなく ReactDOM.hydrate
が実行される。
https://ja.reactjs.org/docs/react-dom.html#hydrate
ReactDOM.hydrate(App, document.getElementById('root'))
ReactDOM.hydrate
は、 サーバサイドで生成された HTMLのDOM構造と、クライアントサイドで生成された仮想DOMが一致していることを期待している。
一致していれば、DOMの再レンダリングをスキップし、イベントリスナーの登録だけを行う。
もし一致しなければ、(可能な限り)DOMの状態を再現するために再レンダリングを行う。
React はレンダーされる内容が、サーバ・クライアント間で同一であることを期待します。React はテキストコンテンツの差異を修復することは可能ですが、その不一致はバグとして扱い、修正すべきです。開発用モードでは、React は両者のレンダーの不一致について警告します。不一致がある場合に属性の差異が修復されるという保証はありません。これはパフォーマンス上の理由から重要です。なぜなら、ほとんどのアプリケーションにおいて不一致が発生するということは稀であり、全てのマークアップを検証することは許容不可能なほど高コストになるためです。
つまり
サーバサイドで生成しテキスト化したDOMと、クライアントサイドで生成(計算)したDOMとの間に、1文字でも差分が生じると、ページ全体が再レンダリングされることになる。
特定のコンポネント一箇所だけで起こっていたとしても、ReactDOM.hydrate
はルートエレメントに対して行われるため、対象コンポネントだけ再レンダリングというわけにはいかない。この処理がかなり高コストであり、非力なモバイル端末だとパフォーマンスに影響がでる。
対処
対処方法に関しては、下の記事を参考にされたし。
https://zenn.dev/takewell/articles/5ee9530eedbeb82e4de7
先に述べた通り、useEffect
は Hydrate 後に行われるため、useEffect
と useState
を駆使して、マウントされているかどうかで分岐すると問題は最小限に留められる。
(「最小限に留められる」というのは、あくまでレンダリングを遅延させただけで、そのコンポネントに関してはSSG/SSRの恩恵を受けられなくなるため。)