Next.jsから学ぶWebレンダリング ~React誕生以前からApp Router with RSCまでの流れ~
最近話題のReact Server ComponentsやIslands Architectureが何を解決しようとしてるか知るまでの簡単なWebレンダリングの流れを記載しました。
社内勉強会のために作成した資料となるため箇条書きになっておりますが、なるべくHowやWhatではなくWhyやトレードオフを記述するようにしています。(読みにくい or 誤った記載あったらFB頂けたら幸いです)
React 誕生までの Web
iPhone と Ajax がリードした Web 2.0 時代
- Webにおいて Ajax という技術が注目され始める 2005~
- Google mapsやGmailといったサービスがリード
- jQueryの誕生が 2006~
- iPhone登場 2007~
- スマホアプリの登場によりソフトウェアのUXに求められる質的変化
- mobile safariが時代のリードをした
- Flashの終焉、代替とされたHTML5,CSS3策定など
- Webページではなく「Webアプリ」が作られるようになった時代
- 技術とデバイスの両輪により、現在に続くSNS等の多様なプロダクトが誕生した時代 = Web2.0 ※諸説アリ
Webアプリのフレームワーク
- 複雑なWebアプリをピュアなJavaScriptやjQueryだけを用いて構築/運用するのは難しかった
- そのためベストプラクティスや規約を提供するフレームワークが群雄割拠した
- Backbone.js(2010~)やAngularJS(2009~)やKnockout.js(2010~)が代表例
- MVVMといった当時主流のアーキテクチャは、現在もモバイルアプリ開発などでは一般的な設計方針
- Backbone.js(2010~)やAngularJS(2009~)やKnockout.js(2010~)が代表例
この時代までのWebレンダリング
代表例: RoR, Erb + jQuery, CoffeeScript(Railsに採用されたaltJSとして一時流行ってた)
<%= link_to "記事を削除", @article, remote: true, method: :delete %>
<%= form_with(model: @user) do |f| %>
<%= f.label :name %><br>
<%= f.text_field :name %>
<%= f.submit %>
<% end %>
$ ->
$("a[data-remote]").on "ajax:success", (event) ->
alert "この記事を削除しました"
// こんな事もできる
$("<%= escape_javascript(render @user) %>").appendTo("#users");
Rails, PHP時代のポイント
- サーバーサイドのフレームワークに結びついたテンプレートエンジンがHTMLやJSを生成する
- ユーザーが最初に目にするインターフェース=HTMLを返すのはサーバーの仕事だった
- UIの形成含めブラウザ上でも良いUXを実現するための主要な責任はサーバー側にあった
- フロントエンドエンジニアという職能は存在せず、代わりにマークアップとスタイリングのみを担うコーダーという職種が存在
- クライアントの仕事はテンプレートエンジンに渡されたオブジェクトを用いて、HTMLとCSSを書きjQueryなどを用いてDOMにイベントをアタッチしていくイメージ
- もちろん当時からコンポーネントやモジュールでフロントエンドを管理するといった考え方は存在しており、MVVMやPub/Sub(event bus)といった設計パターンが用いられ、コンポーネントを分割しても状態管理やイベント伝播を試みていた。
React の登場
Reactを一言であらわすとしたら UI = f(state)
状態を定義すればそれがUIになるというもの。宣言的UIと呼ばれるパラダイムを作ることになる。
Webでは仮想DOM技術によって、設計したものを素直に書くことで実用的なスピードでレンダリングされることになり、宣言的UIの時代となる。(以降Swift UIやFlutterなどモバイルアプリ開発にも宣言的UIのコンセプトは反映され、UI制作のデファクトスタンダードとなっている)
React SPA時代のポイント
- テンプレートエンジンの重要度が下がった
- UI形成やそれに伴うブラウザ上でのUXの責任はクライアントの仕事に移ってきた
- フロントエンドエンジニアという職能の誕生
- とはいえRuby on RailsやDjangoの上でReactやVueを使うことは一般的であり、適切なHTMLを返した上でSPAにすることは難易度が高かった。
- UI形成やそれに伴うブラウザ上でのUXの責任はクライアントの仕事に移ってきた
- Web2.0時代に普及したものとして他にWeb APIがあった(TwitterやGoogle Map APIなど)
- 非同期通信をもとにしたSPA開発を後押し
React(CSR)のトレードオフ
- メリット
- Webアプリの時代にあった滑らかな体験を作りやすくなった。
- サーバー費が安く、スケールの見通しを立てやすい。
- FCPこそ遅いが、一度メインのJSをキャッシュした後は旧来のSSRアプリよりも快適に動作する場合がある。(ユーザーへの細かなフィードバックや、インタラクティブなページ・コンテンツ切り替えに対する体感が早い)
- キャッシュをうまく利用できるとネットワークコストのメリットを得られる。
- デメリット
- モバイルでは通信速度などが影響しサイトのパフォーマンス保証が難しい。真っ白な画面が長時間表示されてしまうという問題が起こるサイトもあった。
- インタラクティブが薄いページやアプリに対してCSRを採用することは、パフォーマンスのデメリットに対するメリットが少ない。
- SEO的に不利と言われる時代があった。
- Webはページを元に発展してきたため、Web標準の準拠に難しさが生じることがある。
Next.jsの登場とWebレンダリングの変化
Next.jsによるSSRとは何であったか
2016年に登場したNext.jsはReactをNode.js上で動作させるようにした。
Next.js SSRのポイント
- JavaScriptを書くエンジニアがHTML,JSを適切な状態でユーザーに届けるための責任をすべて追うことができるようになった。
- サーバーサイドとフロントエンドの境界が難しくなり、BFF(SSR含む)までをフロントエンドエンジニアの仕事とする組織が増えた。
- SSRによって適切なHTMLやCSSを返せるようになったことで、真っ白な画面が表示されるみたいな現象はなくなったが、一方でハイドレーションという技術課題が発生することになった。
Next.js公式サイトより引用
SSRのトレードオフ
- メリット
- 何でも作れる(一番フレキシブル)
- デメリット
- リクエストごとにサーバーサイドがHTMLを生成するためサーバーのコストが高くなったり、スケールの問題を抱えやすい。
- ハイドレーションの課題に対する方針や対策が必要な場合がある。
- モバイルにおいてはハイドレーションが数秒から数分かかるケースもあるそう
- ちなみにハイドレーションの問題はReact以降固有ではない。JS, jQueryの時代からHTMLのレンダリングを優先してJSをdeferやasync(DOMContentLoaded)で実行した場合、表示されてるのにクリックしても何も起きないボタン問題は昔から存在した。バンドルサイズが増えたことで顕在化しやすくなったが。
CDNの進化とSSG
Next.js SSGのポイント
- ReactがServerで動作することで、サーバーで都度HTMLを生成するのではなく、デプロイ時などにあらかじめHTML/JSを生成して、CDNを通しStaticとして配信できるようになった。
- Next.jsが登場した同時代にFastlyやCloud FlareといったCDN(Edge Side Cloud Computing)が進化しており出来ることが広がったため、会員制サイトなどでもこのSSGが用いられるようになった。
- 日経電子版やDev.toやZenn.devなどのCDNとSSGで作られたサイトが速くて界隈で話題になった。
- Reactと距離を取っていたサーバーサイドエンジニアも、Next.jsや周辺のレンダリングと配信の仕組みに興味を持つ人が増えた印象
SSGのトレードオフ
- メリット
- サーバーが存在しないためスケールとコスト面で最強
- デメリット
- 要件が満たせないサービスが存在する
- アプリやSaaS系はおおよそSSGで代替できない。
- 会員制かつあるユーザー特有の情報を表示しなくてはならない場合、他のアーキテクチャの方が適する可能性が高い
- 要件が満たせないサービスが存在する
SSGのNext.js派生のISRとはなんであったか。
- 時間やイベントなどのトリガーを元にSSGを行うNext.js(Vercel)が発表した仕組み
- コスト面の最適化など難しく、あまり普及してないイメージ
- デプロイフローやサーバーだけで完結させずに、CDN最適化まで含めると例えばSSRの結果を30分キャッシュするみたいなことも可能になるが、そういう構成の方がインフラへの依存が少なかったりコンピュータリソースも少なく済んだりするケースもあり、どういう場合にISRが適するか判断するのは難しそう
- とはいえオプションが一つ増えたのは便利な場合もある。
ここまでのまとめ
ベターなWebレンダリングのためには下記が推奨された。
- 可能なユースケースではSSGを優先する
- ハイドレーションに配慮したSSR
- 可能なケースはSSR結果のHTML,JSをCDNでキャッシュすること
- とはいえこのハイドレーションに配慮することが難しかった
- CSRは消極的なオプションだが、PC中心などモバイルユーザーが少ない(ネットワーク環境が保証される)サービスでは、ユーザー体験が良くインフラ的にも配信しやすい可能性がある。B2BのSaaSなどはこの領域が多い。
- Slackのようなコンテンツとリクエストが豊富なSPAになると、他のボトルネックがいくらでもあってレンダリングオプションは誤差になりえる。
React Server ComponentsとNext.js App RouterはWebレンダリングで何を目指しているのか
React Server Componentsとは
uhyoさんの記事が分かりやすかった為引用。
RSCの最も基本的なメンタルモデルは、「Reactアプリケーションの中に、サーバーで実行される部分とクライアント側で実行される部分がある」ということです。
「プログラムの評価を多段階に分けて処理」2段階の計算の場合は「stage 0のプログラム」と「stage 1のプログラム」があります。
→ これまでのSSRはStage1でReactをNode.jsで動かした状態。今回のRSCはStage0を追加した。サーバーサイドでしか動作できないため、例えば useState
といった状態管理の機能は使えない。
RSCのポイント
- Stage0によってUIの構築に関係ないコードをStage1から除外できる
- バンドルサイズの削減を実現できる可能性がある
- ReactチームはゼロバンドルサイズのReactにも取り組んでいる
https://legacy.reactjs.org/blog/2020/12/21/data-fetching-with-react-server-components.html
- よくNext.jsやNuxt.jsではClientとServerのコードが混ざってしまう事によるセキュリティリスクなどが言われてきたが、Server ComponentはClientに配信されない
- ClientとServerのコードは共通化せずに(型やバリデーションやスキーマは共通化した方が良い場合も多いが)明示的に分けた方が良いことが、コミュニティの発展とともに共通認識になってきた。
- Server ComponentはuseStateを使えない一方、Next.jsでトップレベルページでしか使えなかったgetServerProps()などがどこでも使えるようになる。
- 特にReact QueryやSWRを使ってたりする場合、結構設計が変わるかも。
- fetchとかcacheどうする問題
- コンポーネント設計にも状態管理にも設計の影響を与えうる変更。
- 特にReact QueryやSWRを使ってたりする場合、結構設計が変わるかも。
Next.js App Routerとは
To make our router compatible with streaming, and to solve these requests for enhanced support for layouts, we set out to build a new version of our router.
→ RSCやSuspenseによってReactのUIは、Concurrent RenderingやPartial Hydrationを目指すことが可能になってきた。そのため、この時代のRouterの作りをPageからAppへと見直している。
またReact公式もRSC以降に関しては、Create React Appなどで純粋なReact SPAを作ることは非推奨となり、Next.jsやRemixなどのラッパーフレームワークを通すことを推奨している。
Next.js App Router with RSCのトレードオフ
- メリット
- SSRの中では最もクライアント的な体験の良いアプリを作れるポテンシャルがある
- SSGを行う場合においても、RSC使うことでバンドルサイズが減らせる可能性がある
- 名前の印象からか誤解してる人もいるが、RSCした上(Stage0を走らせた上)でSSG出来るので単純に配信するバンドルサイズの削減に効果がある
- もちろんCSRの場合でも同様なのでRSCを使うことに開発的な難しさを除き、機能的なマイナスはない。
- デメリット
- SSRを利用する場合はSSGやCSRと比べてサーバーは必要になる
- Vercelがサーバーを売るためにこういうムーブメントにしてるんじゃないかという批判もあったりする。
- 私は上記は同意していない。純粋にUXを良くするための設計を追求してるように感じるし、またApp RouterもRSCいずれもSSR固有のものではない。SSGやCSRと併用して使うことも可能。
- フィットするかどうか組織による印象が強まった。
- 実際にReactを開発するMeta社はNode.jsの会社ではないので、React自身がRSCの開発を自社で完結できずVercelのような外部のパートナーと組んでいる。
- 設計が変わる。良いアプリを作るための設計や書き方はより難しくなった(求められるコンセプトへの理解が増えた)印象。Next.jsが難しくなったという意見もよく見る
- 個人的には、FetchとCache周りの状態管理などのベストプラクティスや設計が固まってこないと普及しないのではという印象
- Reactを日常的に書いてキャッチアップしてる私ですらこういう意見の気持ちが分かる
- SSRを利用する場合はSSGやCSRと比べてサーバーは必要になる
その他の流れ
- SPAではなくMPA にすることでパフォーマンスの安定やWeb標準の準拠しやすさを向上させようという派閥もあり、 Astro というフレームワークが一定支持を集める。
- Deno による WebはSSRに回帰する という記事が話題になる。
- ちなみにFresh(Denoのフレームワーク)やAstroが採用している Islands Architecture はReact Server Componentsとアーキテクチャがとても似ている。
- RSCはMPA Island Architectureによるroutingなどの欠点をなくして、SPAにしたものとも言われる。
https://twitter.com/sebmarkbage/status/1570901159434670080?s=20
- RSCはMPA Island Architectureによるroutingなどの欠点をなくして、SPAにしたものとも言われる。
- 最近登場したReact Server Components, Islands Architecture(Astro, Fresh), Qwikはハイドレーションの課題を解決しようとしていると考えると潮流が分かりやすい。
- SSRの課題を解決するためのPartial Hydration
- ちなみにFresh(Denoのフレームワーク)やAstroが採用している Islands Architecture はReact Server Componentsとアーキテクチャがとても似ている。
- SSRの派生としてEdge Computing上でレンダリングすることにフォーカスした、 Remix のようなフレームワークも存在し、ESRと呼ばれることもある。
- Staticなコンテンツを配信するだけだったCDNの時代から、Edge Computingによるレンダリングの実現はしやすくなっている。
Discussion
各々の動きは掴めていたつもりでしたが、こうやってまとめていただくことでそれぞれが繋がり理解が深まりました。ありがとうございます