Gatsby でサイトを構築していて、開発サーバーでは起きなかった表示崩れがプロダクションビルドで発生しました。
初期表示時にFOUC のような画面のがたつきや、ダークモードのちらつきがプロダクションビルドのみに起きるというものです。
原因は SSR と Hydration でしたので調べたことをまとめます。
exampleを clone して yarn build && yarn serve
npx gatsby infoSystem:OS: Linux 5.4 Debian GNU/Linux 10 (buster) 10 (buster)CPU: (8) arm64 unknownShell: 5.7.1 - /usr/bin/zshBinaries:Node: 16.10.0 - ~/.anyenv/envs/nodenv/versions/16.10.0/bin/nodeYarn: 1.22.11 - ~/.anyenv/envs/nodenv/versions/16.10.0/bin/yarnnpm: 7.24.1 - ~/.anyenv/envs/nodenv/versions/16.10.0/bin/npmBrowsers:Firefox: 78.15.0esrnpmPackages:gatsby: ^4.0.2 => 4.0.2gatsby-plugin-image: ^2.0.0 => 2.0.0gatsby-plugin-layout: ^3.0.0 => 3.0.0gatsby-plugin-mdx: ^3.0.0 => 3.0.0gatsby-plugin-sharp: ^4.0.1 => 4.0.1gatsby-remark-copy-linked-files: ^5.0.0 => 5.0.0gatsby-remark-images: ^6.0.0 => 6.0.0gatsby-source-filesystem: ^4.0.0 => 4.0.0gatsby-transformer-yaml: ^4.0.0 => 4.0.0
gatsby のビルドプロセスはの違いはドキュメント の通りです。
ざっくり言うと gatsby build
では全てのページの HTML をビルド時 SSR(SSG) し、Hydration を通してクライアントサイドに React アプリを引き継ぎますが、
gatsby develop
では SSR しませんので全てのレンダリングがクライアントサイドで行われます。
これは SSR 全般に言えることだと思いますが、レンダリングを行うのはサーバーサイド(この場合 Node.js 環境)なのでレンダリング時にクライアントの情報は分かりません。(window オブジェクトや CSS MediaQuery 等。"シュレディンガーのユーザー" )
window
や デバイス情報に依存したコードが含まれている場合、SSR された HTML は完全ではないので two-pass
レンダーによって完了します。
Gatsby does a two-pass render for HTML. It loops through your pages first rendering only the body and then takes the result body HTML string and passes it as the body prop to your html.js to complete the render. source: #onRenderBody
これがちらつきの原因となり得ます。 開発サーバーでは全てのレンダリングをクライアントサイドで行うので問題は起きなかったという訳です。
今回僕は Adobe のデザインシステムである Spectrum の React Spectrum
を使用していて、
Theming とかは全てお任せしていたわけですが、Spectrum のカラーテーマは preferes-color-scheme
メディアクエリに従います。
当然 SSR 時は検出できませんので、設定は遅延されます。
結果、ダークモードが設定された OS でリクエストした場合、SSR で生成された明るいテーマの HTM が表示されたあとに、dark テーマで再レンダーされる最悪の表示になっていました。
React Spectrum はcolor-scheme
以外にもロケールによってフォントファミリーだったり、any-pointer
でアクセシビリティ対応とかしてくれてるっぽいのでその辺のデフォルト値を設定することでとりあえず解決しました。
(基本的に PC からのアクセスを想定しているので、デフォルト値を PC 用に設定した)
カラーモードは諦めました。
<Providertheme={darkTheme}colorScheme="dark"scale="medium"locale="ja-JP"breakpoints={{ S: 0, M: 768, L: 1024 }}/>
他にはこちら の通り DOM にマウントされる前は何も render しない(SSR 時には何もレンダリングしない)というワークアラウンドもあるようですが、 初期表示を高速にできるメリットは失われますね。SEO は詳しく無いので SEO への影響は分かりません。
prefers-color-scheme
クエリで OS のカラーモード設定に従うのは良いプラクティスだと思うのですが、SSR とはトレードオフということですかね。
(レスポンシブのちらつき等も)
develop モードでも SSR するようなフラグが追加されている?みたいです。 #28138