🧙

React Server Components は Web アプリ開発にどのような変化をもたらすか

2023/06/29に公開

TL;DR

  • React は単なる UI ライブラリにとどまらず Routing, Bundling, Server Technologies までを統合した技術になろうとしている
  • React Server Components はパフォーマンスだけでなく、「PHP, Ruby on Rails のシンプルなサーバサイド HTML 描画の世界に戻しつつ、サーバサイドとクライアントサイドの実装を同じ技術でシームレスに実装できるようにする」ことができる
  • それにより、開発者から見た複雑さを下げられ、かつエンドユーザは良いパフォーマンス・良い体験を得られる

React Server Comonents (以下、RSC)について、インターネット上の記事では主にパフォーマンスについて語られることが多い印象です。しかし、RSC のもう1つ重要な点として、RSC は我々の Web アプリケーション開発の体験自体を大きく変える可能性を持っていると筆者は感じています。

RSC のパフォーマンス以外の側面については、以下の記事などでも紹介されています。

https://zenn.dev/uhyo/articles/react-server-components-multi-stage

この記事では、コードを書く・技術選定をする人間の視点で、RSC が我々の仕事にどのような変化をもたらすかという点に重きを置きつつ、筆者なりの理解を解説していきます。

React Server Components

React は単なる UI ライブラリにとどまらず Routing, Bundling, Server Technologies までを統合した技術になろうとしているようです。

As we’ve explored how to continue improving React, we realized that integrating React more closely with frameworks (specifically, with routing, bundling, and server technologies) is our biggest opportunity to help React users build better apps.

https://react.dev/learn/start-a-new-react-project#bleeding-edge-react-frameworks

その中で重要な技術であり、具体的な動きの一つが React Server Components (RSC) です。

RSC 自体が発表された当時は筆者的には「またなんか難しいことやっとるな?」みたいな感じでした。しかし、Next.js v13.4 で App Router が Stable になったことで、我々が現実的に扱いうる問題まで降りてきています。技術選定などにも影響するため、理解し見極める必要があります。

RSC の RFC に記述されているモチベーションをすごく雑にまとめると「データ取得をより簡単し、普通に開発してたら良いパフォーマンスが達成できるようにしたい」だそうです。

These challenges fall into two main buckets. First, we wanted to make it easier for developers to fall into the “pit of success” and achieve good performance by default. Second, we wanted to make it easier to fetch data in React apps.

しかし、先に述べた通り RSC にはパフォーマンス以外の重要な側面があると筆者は考えています。それは「PHP, Rails のようなシンプルなサーバサイド HTML 描画の世界に戻しつつ、サーバサイドとクライアントサイドの実装を同じ技術でシームレスに実装できるようになる」という点です。記事ではこの側面について述べていきます。

前提: 動的な Web アプリケーション実装の変遷と課題

動的な Web アプリケーション実装の変遷(雑)

この節は観測をもとにした推測からの雑な議論です。

  1. サーバが HTML を組み立て、それをクライアント(ブラウザ)が表示するだけ
    • UI はすべてサーバが組み立てている
  2. サーバが HTML を組み立て、それをクライアントが表示するが、ページの一部分はクライアント上の JavaScript(jQuery とか) で動かす
    • UI の大部分はサーバで、一部はクライアントで組み立てる
    • サーバとクライアントは別技術になる
      • e.g. PHP, Rails
      • サーバが Node.js だったとしても、サーバ・クライアントで同じテンプレートエンジンを使えるわけではない
  3. サーバは JSON だけ返すようになり、UI はクライアント上の JavaScript で組み立てる(JSON色付け係!)
    • UI はすべてクライアント上の JavaScript で作る世界
      • e.g. React, Vue.js, Angular
  4. クライアント上で動かしていた JavaScript による UI 構築をサーバで動かすことで、サーバが HTML を返す(SSR: Server Side Rendering)
    • UI はすべてクライアント上で動く技術(JavaScript)で作るのは変わらず
    • クライアント上で UI に動きをもたせるために、サーバが返した HTML をクライアント上の JavaScript アプリに再構築(Hydration)する

時代と技術が進むごとに、徐々にクライアントもしくはクライアント上で動く技術(JavaScript)に UI 構築の責務が寄ってきています。これは以下のような理由によるのではないでしょうか。

  • いいユーザ体験のために部分的にリッチな UI が必要で、そのためにクライアントサイド JavaScript はほぼ不可避である
  • とはいえサーバとクライアントは別技術なのに同じ UI をそれぞれで作るのは大変なので、より表現力の高いクライアント技術に寄せていく

このような流れを辿った結果、現在はクライアント上で動く JavaScript を使って UI 全体を作ることが増えているのでしょう。

(もちろんそうじゃないケースもある。あくまで割合が増えてそうだよねって話。)

SPA・SSR による Web アプリケーション実装の課題

ほとんどの Web アプリの UI は、以下の2つの側面を兼ね備えています。

  • インタラクティブ性が必要のない static な部分
  • インタラクティブ性と即時応答(ユーザの操作がすぐに UI に反映される) dynamic な部分

余計な dynamic さ

しかし、前述した理由などで、現状の技術では本来的には static でいいところまで含めた全体を dynamic に作ることがあります。

サーバとクライアントは別技術なのに同じ UI をそれぞれで作るのは大変なので、より表現力の高いクライアント技術に寄せていく

その結果何が起きるでしょうか。

dynamic さはユーザ PC に負荷をかける

わかりやすいところでいうと、余計な dynamic によるユーザ視点のデメリットがあります。

ページを dynamic にするには、そのために追加でスクリプトが必要になるでしょう。そのダウンロード・実行が、ユーザ体験にネガティブな影響をあたえるかもしれません。

我々開発者はスペックの高いマシンを使いがちなので気づきづらいですが、ユーザが使っている端末によっては、余計な dynamic さによってめちゃくちゃ体験が悪くなってる可能性もあります。

dynamic さは複雑さを招く

開発者視点では、 dynamic さのが招く余計な複雑さはデメリットになります。たとえば、static なら1度データを取得できればいいだけなのに、useEffectuseQuery のような難しい仕組みを使わないといけないし、使い方をミスると謎のバグりかたをする みたいな。

SSR についても、サーバでもクライアントでも動くコンポーネントを作ることになるため結局は dynamic であり、複雑性はむしろ増していると言えるかもしれません。

また、複雑なコードは開発者の時間を余計に消費しがちです。デバッグの手間もかかるし、あとから来た人がコードを読むときに余計な負荷をかけてしまいます。なにより、実装がめんどくさい。本来は データ取得 → if 分岐 / for ループのシンプルなコード → HTML のプリント で良かったのに、なんでこんなコード書かないといけないんだろう 的な。

それが必要な複雑さな場合もありますが、static でいいところを dynamic にしているのは余計な複雑さであり、可能であれば排除したいものです。

React Server Components が解決する課題ともたらす世界

ここまでの話から、現状の(動きのある)Webアプリ開発における課題を static/dynamic の観点からまとめてみると、以下のような感じでしょうか。

  • アプリケーションの dynamic さは余計な複雑さを生むので、できれば static に寄せたい
    • PHP や Rails でやってたくらいシンプルに HTML を組み立てたい
      • e.g. データ取得 → if 分岐 / for ループのシンプルなコード → HTML のプリント
  • 一方でユーザ体験を良くするために回避できない(しづらい) dynamic さもある
    • e.g.
      • フォーム上のちょっと複雑なフィールドの表現
      • 早いタイミングでのバリデーション実行とフィードバック
  • static と dynamic の両立をしようとすると、これまでは実装技術(言語・フレームワークなど)を分けるしかなかった
    • SSR はサーバでやってるとはいえ実装的には dynamic さのための複雑性を排除できていなかった

この課題をうまく解決する、技術の分裂を起こさず、必要なところにだけ dynamic さを持ち込めるものが React Server Components だと筆者は考えています。

RSC でどうやって余計な dynamic さを落とすか

そもそもが dynamic な(動きのある)ところはある程度の複雑さを受容して頑張るしかありません。しかし、それ以外の static な(動きのない)ところは static にして複雑さを捨てるに越したことはないでしょう。実装時に考えないといけないことがかなり減らせるはずです。

これは前述した歴史で言う2番目、「サーバサイドの PHP や Rails で描画した HTML をクライアントサイド JavaScript で動かす」という世界に近いでしょう。当時は「サーバとクライアントで技術が別」であることで開発者から見た複雑さが増大していたのが大きな課題でした。

そして、これを解決できるのが RSC です。

RSC の要素のうち Server Components と呼ばれるものは、サーバ側で1度 HTML になってクライアントに返ると React の世界に戻ってこなくなります。その関数(コンポーネント)は1度だけしか実行されません。ややこしい状態遷移のことは考えず、「返ってきたデータを描画するだけ」でよくなります。

下記は Server Component のサンプルコードです。取得したデータをつかって HTML を描画しているだけで、非常にシンプルです。 useEffectuseQuery のような React 独自の要素は出てきません。 await しているので loading state のことも考慮する必要がなくなっています。

// 公式のデモのコードを改変したもの
// https://github.com/reactjs/server-components-demo/blob/e315f17/src/NoteList.js

export default async function NoteList({searchText}) {
  const notes = await (await fetch('http://localhost:4000/notes')).json();

  return notes.length > 0 ? (
    <ul className="notes-list">
      {notes.map((note) => (
        <li key={note.id}>
          <SidebarNote note={note} />
        </li>
      ))}
    </ul>
  ) : (
    <div className="notes-empty">
      {searchText
        ? `Couldn't find any notes titled "${searchText}".`
        : 'No notes created yet!'}{' '}
    </div>
  );
}

Server Component で Client Component(クライアント上で動作する、動きのあるコンポーネント)を返せば、それはクライアント上で Hydration され従来の React アプリと同じように動くことになります。

たとえば先程のコード中の SidebarNote 内で当たり前のように呼ばれている SidebarNoteContent は Client Component であり、 onClick などのイベントハンドラが実装されています。サーバ側 HTML 描画とクライアント側コンポーネントがサーバ側のシンプルな記述を維持しつつシームレスにつながることは、いままでなかったのではないでしょうか。

このように、PHP, Rails のシンプルなサーバサイド HTML 描画の世界に戻りつつ、サーバサイドとクライアントサイドの実装を同じ技術でシームレスに実装できるようにすることで、開発者から見た複雑さを下げ、かつエンドユーザは良いパフォーマンス・良い体験を得られる - というのが RSC が実現してくれる世界だと考えています。

RSC へのよくあるツッコミと回答

RFC の FAQ に書いてあるものばかりですが、とくにありそうなやつを筆者の視点から雑に解説しておきます。

  • Server Side Rendering(SSR) でしょ? ウチはいらなくない?
    • 「React をサーバで動かす」というと SSR を連想し「ウチは SEO や OG が不要だから関係ない」みたいな話になりがち
    • そもそも RSC は SSR と解いてる課題も性質も大きく異なっている
      • e.g.
        • 従来の SSR は Hydration があるので結局クライアントでも JavaScript が必要になる
          • dynamic さ由来の複雑性は捨てられていない
        • RSC の Server Component はサーバで HTML を組み立てて JavaScript は返さないので、クライアントでは JavaScript が不要
      • RSC の Client Component を SSR することがあれば、それは従来の SSR とだいたいおなじ
  • PHP じゃん
  • React から DB につなぐやつでしょ? 気持ち悪くない?
    • 「こんなこともできるよ!」であって、「こうすることになるよ!」ではないです
    • DB 直アクセスもできる という選択肢が増えたことが重要
      • たとえば、プロダクト立ち上げ直後でスピードがなにより重要なとき DB 触れたほうが便利かもしれない

RSC の未解決問題・欠点

RSC はまだまだ発展途上の技術であり、未解決の問題も残っています。RFC の Open Areas of Research にリストがあるので、目を通してみるといいでしょう。

欠点も同様に RFC の Drawbacks に記述があります。Server Component 特有の制約や、Server Component, Client Component, そして “shared” なコンポーネントの区別のための規約などは悩ましいですね。

個人的には…

RSC が成熟したあとの React は次の Rails (の Controller と View を React で最強にしたやつ)になりうるかもと思っています。いま Bet すると超楽しそう。

また、RSC により複雑な記述が減ったあとの Web Frontend でこのあとどんな大きな変化が待っているのか, 誰かが React を倒すのか, そして Copilot やその延長にある AI 的な技術によってこの先どうなっていくのか、ワクワクしますね!

参考URL

Discussion