Upgrade to Pro — share decks privately, control downloads, hide ads and more …

React Server Components の疑問を解き明かす

mizdra
August 09, 2024

React Server Components の疑問を解き明かす

builderscon 2024 で発表した資料です。

動画はこちら: https://www.youtube.com/watch?v=_Prly-RAF7A&list=PLZjwpOgJkJXteFpwAbcVoQXfd2f5q-OPp

---

React Server Component は、サーバーサイド上でレンダリングされる React コンポーネントです。React Server Component を用いると、コンポーネントの中から DB にアクセスしたり、ファイルシステムにアクセスしたりできます。

一方で、今までにも erb や pug といったテンプレートエンジンのように、サーバーサイド上でテンプレートをレンダリングする技術というのは存在しました。また、React でも SSR を用いれば、(Client) Component をサーバー上でレンダリングできました。そのためこれらの技術とどう違うのか、疑問を抱いている方も多いでしょう。

しかし、React Server Component は従来の技術への単なる回帰ではありません。今まで存在していたいくつもの問題を解決し、優れた開発体験をもたらす技術なのです。

このトークでは、React Server Compomnent とは何か、どんなことができるのか、そして従来の技術とどう違うのか、どう開発スタイルが変わるのかについて話します。このトークが、React Server Component に関する様々な疑問を解消する手助けになるはずです。

mizdra

August 09, 2024
Tweet

More Decks by mizdra

Other Decks in Technology

Transcript

  1. React Server Components (RSC) • 新しい種類の React コンポーネント • 特徴

    ◦ サーバで"のみ"でレンダーされる ◦ サーバ専用 API が使える ◦ ブラウザ向けの bundle size を削減できる • Next.js などで利用可能 4
  2. 目次 第1部: SPA と SSR 👈 NEXT 第2部: SSR が抱える問題

    第3部: RSC の誕生 第4部: 従来の技術との違い 第5部: 開発スタイルがどう変わるか 10
  3. Single-page Application (SPA) • 単一のページでリッチな Web アプリを作る手法 ◦ fetch API

    で動的にサーバからデータを取得 ◦ 取得したデータを React などで描画 ◦ JavaScript で画面遷移を制御 • すべて JavaScript で制御することで... ◦ リッチなユーザ体験を実現する 12
  4. SPA が抱える問題 • コンテンツが出るまで遅い ◦ ページアクセス直後は真っ白 ◦ JS が実行され始めて、ようやく出る •

    クローラーとの相性が悪い ◦ 素朴なクローラーだと、空のページと解釈される ▪ Google や Bing なら問題ないけど... ◦ X で og:image が出なかったり 14
  5. Hydration とは? • カピカピの HTML に水を与えて、もとに戻す • だから Hydration (水和)

    19 <button> <button onClick={...}> @HirokiOmote さんの記事の Hydration のたとえを参考に描きました。 https://www.estie.jp/blog/entry/2024/08/05/183235
  6. 目次 第1部: SPA と SSR 第2部: SSR が抱える問題 👈 NEXT

    第3部: RSC の誕生 第4部: 従来の技術との違い 第5部: 開発スタイルがどう変わるか 20
  7. SSR が抱える課題 • いくつか課題がある ◦ 不必要な bundle size の増大 ◦

    データ取得が煩雑 • Next.js で実装した記事詳細ページを例に解説 21
  8. 不必要な bundle size の増大 • <ArticlePage> は、ブラウザ向けに bundle される ◦

    Hydration や、ブラウザ上でのレンダーに必要なので • <ArticlePage> の依存も bundle 対象 ◦ marked, sanitize-html ▪ marked: 11.2 kB (gzipped) ▪ sanitize-html: 80.8 kB (gzipped) • bundle size が大きくなる 23
  9. 不必要な bundle size の増大 • ところで <ArticlePage> は... ◦ ユーザ操作などによって変化しない

    ◦ static なので、サーバでレンダーして終わりで良い ◦ Hydration も不要なはず • 技術的には、bundle に含めないようにできるはず ◦ しかし SSR は、問答無用で bundle に含めてしまう 24
  10. しかし、これはできない • ./db.js がブラウザの bundle に含まれてしまう ◦ ブラウザでは実行できず、エラーになる • 細かい話だけど...

    ◦ そもそもコンポーネントで async/await 使えない (*1) 26 *1: 厳密には使えるが非推奨 (参考: https://github.com/reactjs/rfcs/pull/229)
  11. 目次 第1部: SPA と SSR 第2部: SSR が抱える問題 第3部: RSC

    の誕生 👈 NEXT 第4部: 従来の技術との違い 第5部: 開発スタイルがどう変わるか 27
  12. React Server Components (RSC, SC) • こうした問題を解決するために誕生 • サーバーで"のみ"レンダーされるコンポーネント •

    特徴 ◦ サーバーでしか実行できないコードが書ける ◦ ブラウザの bundle に含まれない ▪ bundle size が削減 ◦ async/await が使える 28
  13. Client Components (CC) • Server Components の対になるもの • その正体は... ◦

    ブラウザでレンダーしていた従来のコンポーネント • Server Components 登場によって... ◦ 名前の整理が行われただけ ◦ 今まで書いてたやつが、Client Components と呼ばれるように 30
  14. 'use client' ディレクティブ • RSC の世界では、デフォルトで SCになる (*2) • CC

    にするには、 ‘use client’ を付ける 31 *2: 厳密には何もつけないと SC / CC のどちらでもない、未確定の状態になる。 コンポーネント利用側に応じて SC / CC どちらになるか、後から決まる。
  15. SC と CC の違い 32 Server Components Client Components ブラウザの

    bundle 含まれない 含まれる async/await ✅ 使える ❌ 使えない (*1) イベントリスナ ❌ 登録できない ✅ 登録できる サーバ専用 API ✅ 使える ❌ 使えない ブラウザ専用 API ❌ 使えない ✅ 使える React Hooks ❌ 使えない ✅ 使える *1: 厳密には使えるが非推奨 (参考: https://github.com/reactjs/rfcs/pull/229)
  16. Client Components は無くならない • 従来の CC だけあった世界に、SC が加わるだけ ◦ できることが増える

    • ブラウザ上でないとできないことは、CC でやる ◦ SC / CC は共存するもの 33 https://github.com/reactwg/server-components/discussions/4 より引用
  17. SC から CC にデータを props で渡せる 35 • シリアライズ可能なものなら OK

    (*3) • シリアライズは React が自動でやってくれる ◦ シームレスに SC と CC を接続できる *3 厳密にはシリアライズ不可能だが渡せるもの (Promise や Server Actions) がある。
  18. RSC と組み合わせて使える機能 • RSC と組み合わせて使える機能がある ◦ Server Actions ◦ Taint

    API • RSC と併用すると... ◦ より優れた開発体験が得られる 36
  19. SC の中で定義して、CC に渡せる • CC に prop で渡せる • 本来

    CC にはシリアライズ可能なものしか渡せないが... ◦ Server Actions は特別に許可されてる • Server Actions は RSC と密に結合されてる ◦ 非常に柔軟なコードが書ける 39
  20. 前提知識 • SC から CC に渡した props はユーザから丸見え!!! ◦ props

    が丸ごとシリアライズされ、HTML に埋め込まれてる 41 コードは以下の記事を参考にしつつ、改変してます。 https://zenn.dev/cybozu_frontend/articles/react-taint-apis
  21. そこで Taint API 42 • 任意の値を汚染 (taint) できる • 汚染された値を

    CC に渡すと... ◦ 実行時エラーが発生する • 誤って CC に機密情報を渡してしまうのを防げる
  22. 改めて: RSC でできること • サーバでのみレンダーされるコンポーネントを作れる • CC でできなかったことが、できるように ◦ サーバでのみ動くコードを書ける

    ◦ bundle size 削減, async/await 使える • Server Actions や Taint API と組み合わせると... ◦ 柔軟なコードを書けたり、よくある間違いを防げたり 44
  23. 目次 第1部: SPA と SSR 第2部: SSR が抱える問題 第3部: RSC

    の誕生 第4部: 従来の技術との違い 👈 NEXT 第5部: 開発スタイルがどう変わるか 45
  24. RSC の場合 • サーバもクライアントも React で書けるから... ◦ 同じやり方でどっちもいける ◦ データの受け渡しも自然

    ◦ Counter をレンダーした状態でサーバから返せる • どっちも React で書けるからこその強み 53
  25. SSR • よく比較されるけど...全く別物 ◦ SSR: Client Components をサーバでレンダー ◦ RSC:

    Server Components そのもの ◦ レンダー手法 ⇔ 特定の種類のコンポーネント 54
  26. RSC を使っていても、SSR できる • SC と CC が混在するツリーに対し... ◦ CC

    部分を SSR する、ことは可能 ▪ 右図の青部分が SSR される • RSC 利用時も SSR は推奨 ◦ Next.js はデフォルトでそういう挙動 ◦ RSC と併用していくもの 55 画像は https://www.plasmic.app/blog/how-react-server-components-work より引用
  27. 目次 第1部: SPA と SSR 第2部: SSR が抱える問題 第3部: RSC

    の誕生 第4部: 従来の技術との違い 第5部: 開発スタイルがどう変わるか 👈 NEXT 56
  28. 変わること ①: data fetch の仕方 • 今までは... ◦ getServerSideProps などから

    data fetch してた • これからは... ◦ RSC から直接 data fetch する 58
  29. 結局のところ... • data fetch のコードを書く場所は変わったけど... ◦ getServerSideProps => RSC •

    data fetch コードの抽象化の仕方は変わっていない ◦ 今まで通りやればよい 63
  30. 変わること ②: SC / CC を使い分ける • 今までは、全部 CC だった

    • これからは... ◦ SC にできるところは SC に ▪ bundle size を減らせるので • 面倒に感じるかもしれないが... ◦ ディレクティブを付けないところは SC になる (*2) ◦ interactive にしたいところに 'use client' を付ければ OK 64 *2: 厳密には何もつけないと SC / CC のどちらでもない、未確定の状態になる。 コンポーネント利用側に応じて SC / CC どちらになるか、後から決まる。
  31. まとめ: RSC とは何か • RSC とは ◦ サーバだけで動くコンポーネント • RSC

    を使うと... ◦ サーバーでしか実行できないコードが書ける ◦ async/await が使える ◦ bundle size を削減できる 66
  32. まとめ: 変わること・変わらないこと • 従来の技術から変わってないこと ◦ サーバーでレンダーされる ◦ data fetch コードの抽象化の仕方

    • 変わったことも ◦ サーバー・クライアント両方を同じ技術 (React) で書ける ▪ 学習コスト軽減, データの受け渡しが自然に ◦ Server Actions や Taint API が使える ◦ CC だけ使う => SC / CC を使い分ける 67
  33. ディレクティブを何もつけない場合は? • SC / CC のどちらでもない、未確定の状態になる • import 元に応じて、後から決まる ◦

    SC から import された時: SC になる ◦ CC から import された時: CC になる • SC /CC どちらとしても使えるコンポーネントを作れる ◦ Shared Components と呼ばれる 70
  34. SC のためのディレクティブは? • 存在しない • ‘use server’ は Server Actions

    のためのもの • SC にするには... ◦ FW が SC だと決め打ちしてるファイルに書く ▪ Next.js なら layout.tsx, page.tsx ◦ もしくは、SC から import する • 若干ややこしい 71
  35. Server Actions の仕組み • bundler がサーバ・クライアントのコードを分離する ◦ 'use server' 部分:

    サーバ向けの bundle ◦ 'use client' 部分: クライアント (ブラウザ) 向けの bundle • action={...} は サーバに POST するコードに置換 72
  36. RSC で他にできること • 他にも色々とできることがある ◦ 段階的なレンダーと Streaming ▪ <Suspense> を境界にして、ツリーの一部分だけレンダー

    ▪ 残りは後からレンダーして、クライアントに Streaming ◦ ビルド時に SC をレンダーできる ▪ Hydration 無しで SSG 相当のことが可能 • 詳しくは RFC を参照 ◦ https://github.com/reactjs/rfcs/blob/main/text/0188-se rver-components.md 73
  37. Islands Architecture • SC/CC と非常によく似ている • ページを static 部分、interactive 部分に分割できる

    ◦ static 部分は hydration されない ◦ ネストもできる • 本質的には、Islands Architecture と SC/CC は同じ! ◦ 同じことができる ◦ では SC/CC の存在意義は何なのか 75
  38. Islands Architecture に対する SC/CC の存在意義 • React 世界における、Islands Architecture の標準化

    ◦ …だと僕は思っている • Islands Architecture は FW ごとにルールがバラバラ ◦ Fresh: islands/*.ts が interactive ◦ Astro: client:* ディレクティブつけたものが interactive • ルールがバラバラだと... ◦ FW ごとに使い方を覚えないといけない ◦ どの FW でも動く interactive component を作るのが困難 76
  39. • 「React ならこのやり方で!」を決めたのが SC/CC • 標準化されることで... ◦ どの FW でも

    (React ベースなら) 同じやり方で OK ◦ Server Component 向けのライブラリが作れる • 標準規格の上に、追加の機能を設けられる ◦ Server Action, Taint API ◦ async/await 77 Islands Architecture に対する SC/CC の存在意義
  40. GraphQL は RSC で使える? • YES • GraphQL は data

    fetch の一手段でしかない • 主な data fetch の手段 ◦ fetch(...) ◦ db.user.get(...) ◦ fetchQuery(...) (GraphQL) • db.user.get(...) を fetchQuery(...) に置き換えたら OK 78
  41. data fetch に GraphQL を使う利点はあるか • まとめてデータを取得できる ◦ ユーザ情報 +

    記事一覧 + 記事ランキング ◦ 1回のリクエストで複数のデータを取れる • fetch (...) を複数回呼んで data fetch するのと比べて... ◦ 総通信時間が減る ◦ "理論上は" GraphQL のほうが速い 79
  42. data fetch に GraphQL を使う利点はあるか • ただし、その速度向上はほんの僅か ◦ AWS なら同一

    AZ 内のマシンとの RTT は 数百 μs 未満 (*5) ▪ 数往復減ったところで、精々 1 ms の短縮になるだけ ◦ 総通信時間の短縮のために GraphQL を使う意味はあまりない • そもそもの話、RSC 実行環境が DB にアクセス可能なら... ◦ db.user.get(...) のほうがずっと効率的なはず 80 *5: 日本国内の AZ においての話。 https://zenn.dev/tsumita7/articles/aws-mesuring-latency-among-az-2024
  43. RSC のイマイチなところは? • SC にする方法がややこしい ◦ ‘use server’ は SC

    にするためのディレクティブではない ◦ ディレクティブをつけないのが正解 • サポートする FW がまだ少ない ◦ Next.js, Waku, Redwood ▪ 安定してるのは Next.js だけ ◦ Vike で実装中 ◦ Remix: v3 (次期 major) でサポートすることに前向き(*4) 81 *4: https://remix.run/blog/remix-v2 より
  44. どのフレームワーク使うと良い? • RSC サポートしてる Production Ready な FW は... ◦

    Next.js (App Router) しかない • RSC 使いたいなら... ◦ Next.js App Router 使うしかない 82
  45. どのフレームワーク使うと良い? • けどフィットするかは、プロダクトによると思う ◦ Edge 最適化のために色々 API が制限されてる ▪ Middleware

    で node:fs モジュール使えないとか (*6) ◦ ルーティング最適化のために API が制限されてる ▪ layout.tsx で searchParams 触れない ◦ 積極的なキャッシュ (v15 で廃止予定ではある) ◦ Server Actions のエンドポイント URL がデプロイ毎に変わる ▪ Vercel の Skew Protection (有料) 導入したら良いらしいが... ▪ そもそもエンドポイントの URL そのままにして欲しい... 83 *6: 最近になって Node.js API をサポートする動きがあるので、そのうち使えるようになるかも https://github.com/vercel/next.js/discussions/46722#discussioncomment-10262088
  46. どのフレームワーク使うと良い? • そこが気に入らなければ... ◦ 他のフレームワークを検討しよう • 例えば Remix とか ◦

    Next.js と大体同じことができる ◦ RSC はサポートされていないが、サポートの計画はある ◦ RSC サポートされたら多少書き方変わると思うけど... ▪ いざとなったらガッツを出して移行しよう 84
  47. RSC 時代の漏洩ポイント • RSC 時代の主な漏洩ポイントは、以下 2 つ ◦ CC のソースコード

    ▪ ブラウザの bundle に含まれるため (P23 参照) ◦ SC から CC に渡した props ▪ HTML にシリアライズされるため (P41 参照) 86
  48. RSC 登場以前の漏洩ポイント • 実は RSC 登場以前も、似たような漏洩ポイントがあった ◦ CC のソースコード ▪

    RSC 登場以前は全て CC だから...つまり全てのコンポーネント! ◦ getServerSideProps から返した pageProps (Next.js) ▪ Remix の loader でも同じ ◦ _buildManifest.js (Next.js) ▪ 全ページのパスとサブリソース (JS, CSS) の URL が載ってる ▪ ページを IP アドレス制限していても丸見えになってる! 87
  49. 漏洩リスクの変化 • 以前のほうが漏洩ポイントは多い ◦ そういう点では、RSC 時代は改善してる • CC や、CC に渡す

    props に機密情報を含めなければ OK ◦ けど、CC かどうか一目で分かりづらい ▪ 'use client' が付いてるものは CC だけど... ▪ 付いてないものは SC にも CC にもなりうる (P70 参照) • そもそも RSC ではサーバ専用 API が気軽に使えるので... ◦ 機密情報を扱う機会も増えてる ◦ 一概に RSC 時代のほうが安全、とも言えない 88
  50. 機密情報の漏洩への対策 • まず漏洩リスクを意識しながらコードを書こう ◦ 当たり前だけど大事 • その上で... ◦ CC や、CC

    に渡す props に機密情報を含めない ◦ Taint API を 使う ◦ import 'server-only' を使う ▪ クライアントの bundle に含まれてしまうのを防げる ◦ 機密情報が漏れてないか、定期的にチェックする ▪ bundle を grep したり、chrome devtools の検索機能を使ったり (*7) 89 *7: https://www.mizdra.net/entry/2023/01/13/123211