yn2011's blog

技術メモ

React Server Components で SSR する場合の Hydration について調べてみた

React Server Components (RSC) について学んでいる中で、Server Component (SC) を SSR した場合に Hydration はどうなるんだろう?と疑問に思ったので調べてみたメモ。間違っていたらすいません。

これまでの Hydration

クライアント側の JS に hydrate すべきコンポーネントが存在するので、普通にhydrateRoot(dom, <App />)

SC の Hydration

SC の JS はクライアントに配信されない。これによってクライアント側のコード量が減るのが RSC の利点の1つ。

では hydrateRoot(dom, <App />) するための <App> をどこから持ってくるか?

Next.jsが出てこないReact Server Componentsハンズオンクライアントサイドの実装 では、RSC のプロトコルを HTML に埋め込み、それを利用して <App> を組み立てていた。

なるほどーと思いつつ、一応 Next.js 13 を利用して RSC を使う場合と使わない場合に生成される HTML や処理を比較してみた。

Next.js における Hydration の比較

Next.js 13 app(beta) の Hydration

Next.js 13 app(beta) では、デフォルトで全てのコンポーネントが SC として扱われ Vercel 等にサーバーとしてホスティングすることで従来どおり SSR を行ってくれる。

コードは以下で、yarn create next-app --experimental-app したものをそのまま利用。

https://github.com/pokuwagata/rsc-ssr-example

Vercel にデプロイした。

https://rsc-ssr-example.vercel.app/

<script>
  self.__next_f.push([
    1,
    'J1:["$","@2",null,{"assetPrefix":"","initialCanonicalUrl":"/","initialTree":["",{"children":["",{}]},null,null,true],"initialHead":[["$","title",null,{"children":"Create Next App"}],["$","meta",null,{"content":"width=device-width, initial-scale=1","name":"viewport"}],["$","meta",null,{"name":"description","content":"Generated by create next app"}],["$","link",null,{"rel":"icon","href":"/favicon.ico"}]],"globalErrorComponent":"$3","children":[null,null,[["$","link","0",{"rel":"stylesheet","href":"/_next/static/css/876d048b5dab7c28.css","precedence":"high"}]],["$","html",null,{"lang":"en","children":[["$","head",null,{}],["$","body",null,{"children":["$","@4",null,{"parallelRouterKey":"children","segmentPath":["children"],"hasLoading":false,"template":["$","@5",null,{}],"notFound":["$","div",null,{"style":{"fontFamily":"-apple-system, ...

HTML を確認すると、このような RSC プロトコルが埋め込まれていて、ここから React コンポーネントツリーを生成して hydrateRoot に渡す模様。

Chrome の Performance タブから Eventlog を見ると以下のように Hydration が実行されている。

通常の Next.js 13 の Hydration

コードは以下で、yarn create next-app しただけ。

https://github.com/pokuwagata/no-rsc-ssr

Vercel にデプロイした。

https://no-rsc-ssr.vercel.app/

<script id="__NEXT_DATA__" type="application/json">{"props":{"pageProps":{}},"page":"/","query":{},"buildId":"lJbXQiPUmF0U5zQNgNPB2","nextExport":true,"autoExport":true,"isFallback":false,"scriptLoader":[]}</script>

HTML を確認すると、このように通常の Hydration 用のデータが埋め込まれているだけなので、Hydration には HTML 文字列をそのまま利用すると思われる(RSC 以前の 典型的 Hydration)

Chrome の Performance タブから Eventlog を見ると以下のように Hydration が実行されている。

RSC の場合に比べると、Hydration 時間が長い。React が解釈しやすいように予め生成されているプロトコルを利用する方が Hydration しやすいということなのかもしれない。

パフォーマンス

Lighthouse で測定して比較してみると、若干 RSC を使っている方が TTI や TBT が小さく出る傾向がありそうだった。

RSC を使った場合

RSC を使わない場合

まとめ

  • RSC を使った SSR では 埋め込まれた RSC プロトコルをもとに Hydration する(と思う)
  • RSC プロトコルを使ったほうが Hydration は短く済む(と思う)