Ccmmutty logo
Commutty IT
2
8 min read

Next.jsにCSRは存在しない

https://cdn.magicode.io/media/notebox/1d25551a-773a-4a99-af0e-52e160d49f4b.jpeg
Next.jsのドキュメントを改めて読み込んでいたところ、Next.jsは所謂CSRから脱却していることが分かりました。 この記事では、Next.jsが提供しているレンダリングの選択肢や、その選択肢にCSRが必要ない理由について解説します。
併せて、このレンダリングについて気軽に動作確認するためのアプリも作成したため、最後にシェアします。

前提

本記事におけるCSRとは、「ページの(再)読み込み時にブラウザに空のdivタグのようなものが返ってきて、DOMの生成からハイドレーションまで全てブラウザ側で行われるアーキテクチャ」を指します。

1. 一般論:SSR / SSG(static) とは

これらの用語は、使う人によって指すところが微妙に異なる場合がある。 CSR以外に、SSR・SSGについても念の為意味合いを明確にしておく。

1-1. SSRとは

ページの(再)読み込み時、以下の条件を満たすページは、SSRされていると言える
  • そのページのHTML生成やハイドレーションより先に、初期描画のためのデータが取得されている
  • そのデータはホスティングサーバー上でリクエストごとに取得されたデータである

1-2. SSGとは

ページの(再)読み込み時、以下の条件を満たすページは、SSGされていると言える
  • そのページのHTML生成やハイドレーションより先に、初期描画のためのデータが取得されている(SSRと同様)
  • そのデータはビルド時に取得されたものである(SSRとの違い)
※Next.jsではStatic Generation=SGと呼んでいるが、便宜上SSGで統一する。

1-3. staticとは

データがない場合には、staticと呼ばれる。 これはSSGの一種とされる場合もある。
例えばNext.jsでは、SSGとstaticを区別しているときもあれば、ドキュメント上staticをSSGの一部として説明している箇所もあり、揺れている。
シーケンスはSSGに近いため、この記事ではstaticはSSGの一種として扱うことにする。

2. Next.jsのレンダリング基本編 ページ再読み込み時

Next.jsのレンダリングは、大きく分けて
  • ページの再読み込み時
  • 画面遷移時 の2つに分かれる。
このセクションでは、SSR/SSGが強みとする、F5やCmd + Rによるページの再読み込み時から説明する。
一般に、CSRは再読み込みに弱いものの、画面遷移が高速であることが強みである。 次のセクションで、Next.jsのSSR/SSGが画面遷移時においてもCSRに遅れを取らないということを説明する。

2-1. SSR ページ再読み込み

Next.jsでは、ページに対してSSRを指定することができる。SSG対象のページをリクエストすると、以下のようなフローになる。
これは一般的に理解されているフローであると考えられる。
  1. ブラウザからホスティングサーバにページをリクエストする
  2. ホスティングサーバーで必要なデータを取得する
  3. ホスティングサーバーはデータをHTMLに組み込んでブラウザに返す
  4. ブラウザは返ってきたHTMLをそのまま表示する(続けてReactのハイドレーション等が実行される)
具体的な実装のドキュメントはこちら。ピンとこなければ参照すること。
SSGは複雑で、実際にはfallbackやISRといった、データの追加・更新に対応するための手段が提供されているが、ブラウザとホスティングサーバー間のやりとりには大枠違いはない。

2-2. SSG ページ再読み込み

Next.jsでは、ページに対してSSGを指定することができる。 SSR対象のページをリクエストすると、以下のようなフローになる。これも、一般的に理解されているフローであると考えられる。
  1. (ビルド時にデータを取得してアセットを生成しておく)
  2. ブラウザからホスティングサーバにページをリクエストする(SSRと同様)
  3. ホスティングサーバー上ではビルド時に生成したHTMLをブラウザに返す
  4. ブラウザは返ってきたHTMLをそのまま表示する(SSRと同様)

2-3. CSRは存在しない

Next.jsではCSRを指定することはできず、ページの再読み込み時に空白ページが返されるようなパターンは存在しない。
もちろん、クライアントから直接データを取得することは可能である。 ページの描画後にuseEffectなどを用いてクライアント側でデータをフェッチする。 ただし、ページの初期表示は必ずSSRやSSG(static含む)によって行われることになり、こうしたレンダリング手法にクライアントサイドデータフェッチを組み合わせることになる。
※「Next.jsにCSRは存在しないか」は、結局のところCSRという言葉の定義次第でもある。クライアントサイドデータフェッチをCSRと呼ぶなら、「Next.jsではSSRやSSGと組み合わせてCSRが可能」ということになる。
クライアントサイドデータフェッチという用語は、Next.jsのドキュメントから持ってきたものである。
このような呼び方にすることで、ページ再読み込み時の初期描画時には必ずサーバー側でレンダリングされたHTMLが返ってくるアーキテクチャ(SSR / SSG / static)であることを示唆していると思われる。

どのようなときにクライアントサイドデータフェッチすべきか

あるデータをクライアントサイドデータフェッチすべきか迷うことがあるとすれば、もう一つの選択肢はSSRになるだろう。 SSGは、データの更新頻度や求められる整合性といった性質が大きく異なるためである。
このとき、どちらにすべきかについては、このページの冒頭にある説明が分かりやすい。
  • Client-side data fetching is useful when your page doesn't require SEO indexing,
  • when you don't need to pre-render your data,
  • or when the content of your pages needs to update frequently.
例えば1画面で収まるTODOアプリがあるとすれば、それはクライアントサイドデータフェッチが向いていると思われる。 ユーザーのTODO追加やステータス更新により、ページ内のデータが頻繁に更新されることになるためである。
一方で、TODOを編集する画面がTODO詳細ページに切り出されているとしたら、SSRが検討できる。 TODO詳細ページのデータは初期表示のタイミングに一度だけ読み込まれれば良いためである。

3. Next.jsのレンダリング応用編 画面遷移時

3-1. SSR/SSGにおける画面遷移時の課題

CSRには、
  • 初回ロード時に大量のアセットを読み込む必要がありロードが遅い
といったデメリットがあるが、それと引き換えに、画面遷移時には高速な遷移を実現可能である
  • DOMの書き換えで画面遷移を実現でき、ブラウザの再読み込みが挟まらない
  • クライアント側に遷移先の画面構築に必要な情報を持っているため、画面構築のための情報をサーバーにリクエストするリードタイムがない
SSGされたページAからページBへ遷移するとき、シンプルにページBの再読み込み時と同様の挙動を示すと仮定すると、画面遷移に関してはCSRに遅れを取ることになる。
  • ブラウザの再読み込みが挟まり、レンダリングの時間が伸びる
  • ページBがstaticなページであったとしても、画面構築に必要な情報をサーバーにリクエストする必要がある
整理すると次のようになる。
当然、Next.jsは、ページ読み込み時のSSR / SSGの強みはそのままに、画面遷移時にもCSRと変わらない遷移速度になるように最適化している。
仕組みは大きく分けて2つであり、これらの仕組みが以下図のようにアクティビティを改善することにつながり、結果的に画面遷移時の体験もベストなものになっている。 数字の順に説明する。

3-2. 画面遷移の仕掛け1:プリフェッチ

Next.jsは、DOM生成のための情報をサーバーにリクエストするリードタイムをなくすために、要素がViewportに現れたら、そのリンク先の画面構築に必要な情報を事前に取得する。これをプリフェッチという(これはデフォルトの挙動である)。
  • 遷移先がSSRの場合、データを含まないDOMやReactに関する情報だけを取得する。
  • 遷移先がSSGの場合、データもコミで事前に取得する。
  • 遷移先がstaticの場合、データはないため、SSR同様DOMやReactに関する情報だけを取得する。
このとき、SSRだけはLinkクリック時にデータだけ取得しにいく。 ページのSSRデータだけを取得可能な専用のエンドポイントが用意されており、そこを叩く。 実際、これはpropsがjsonが返ってくるだけであり、HTML等を一切含まない。
こうして、SSGやstaticでは遷移時のサーバーへのリクエストをなくし、SSRにおいても最小限のデータだけのやりとりで遷移可能となっている。
※CSRはサーバーサイドデータフェッチが発生しないが、代わりにハイドレーション後のクライアントサイドデータフェッチが発生する。SSRが遷移時にホスティングサーバーへのリクエストを必要とするからといって、CSRより遅いというわけではない。

3-3. 画面遷移の仕掛け2:クライアントでのDOM書き換え

Next.jsでは、画面遷移時にはSSR/SSGであっても、クライアントでのDOM書き換えによって遷移の体験を実現している。 例えばページAからSSGされたページBへ遷移するとき、Next.jsは画面遷移専用のページ取得エンドポイントを叩いて主に以下2つのファイルを得る。(名前は適当です)
  • pageB-xxxx.js
    • ページBのHTMLやReactに関する情報が含まれる
  • pageB.json(SSR / SSGの場合)
    • ページBのデータが含まれる
そして、これらを使って、ページを再読み込みすることなくDOMの書き換えを行い、画面遷移を実現する。
プリフェッチが無効化されていない場合、これらのファイルは遷移するときではなく、適当なタイミングで取得される。例えばSSRの場合、pageB.jsonは必要になったときにサーバーに要求するため、プリフェッチではpageB-xxxx.jsのみ取得する。SSGの場合はプリフェッチのタイミングで両方取得してしまうが、一方でISRやfallbackの対策として、リンクへのホバー時や実際の遷移時にもクライアントのパフォーマンスに影響を与えない範囲でpageB.jsonをサーバーに要求している。
これらのファイルは、結局のところ、ページの再読み込み時にページ取得のエンドポイントから返ってくるデータを、概ね2つに分けたものである。2つに分かれている理由は、上記のとおり取得されるタイミングが異なるからである。

4. Next.jsのレンダリング検証アプリ

Next.jsの各種レンダリング方法で、ページ再読み込み時・画面遷移時に実際にどのようなネットワークのやりとりが発生しているか検証できるアプリを作成しました。
トップページから各種レンダリングがなされているページの間に、プリフェッチを検証するためのLinkが存在するページを配置しています。スクロールするとプリフェッチが発生しますので、開発者ツールのNetworkタブなどを開いた状態でスクロールしてみてください。
その他にも、ページ再読み込みなど色々試してみてください。

まとめ

Next.jsにはCSRは存在しません。クライアントサイドデータフェッチをするとしても、ページ再読み込み時の初期描画は必ずSSRかSSGとなります。
Next.jsにCSRが存在しなくても困ることはありません。なぜなら、Next.jsは、プリフェッチや画面遷移用のページ取得エンドポイントといった仕組みにより、SSR/SSGの強みを保ったまま、CSR同等の高速な画面遷移体験を実現しているからです。

追伸

Next.jsのレンダリング周りの設計は美しいですね。
SSR/SSGでCSRと同等の画面遷移体験を実現しているだけではなく、プリフェッチの仕組みにより必要最小限のリソースしかサーバーに要求しない形になっており、CSRはもはや必要ないんだなと関心しました。
後は、SSR、SSG、CSRといった用語が時代についてきていない気がしています。データがいつどこで取得されるかと、HTMLレンダリングがどこで発生するかを区別して考えられる概念体系が必要だと思います。

Discussion

コメントにはログインが必要です。