Slide 1

Slide 1 text

React Server Components の 疑問を解き明かす id:mizdra 2024/08/10 builderscon 2024 1

Slide 2

Slide 2 text

自己紹介 ● mizdra (みずどら) ● インターネット大好き ● 株式会社はてな ○ フロントエンドエキスパート 2

Slide 3

Slide 3 text

3 本日のテーマ React Server Components

Slide 4

Slide 4 text

React Server Components (RSC) ● 新しい種類の React コンポーネント ● 特徴 ○ サーバで"のみ"でレンダーされる ○ サーバ専用 API が使える ○ ブラウザ向けの bundle size を削減できる ● Next.js などで利用可能 4

Slide 5

Slide 5 text

例: RSC で記事詳細ページを作る 5

Slide 6

Slide 6 text

例: RSC で記事詳細ページを作る 6 サーバ専用 API 使える

Slide 7

Slide 7 text

色々な技術と似ている ● テンプレート系 ○ サーバー上でテンプレートをレンダーできる ○ *.erb (Ruby), *.tmpl (Go), *.php ● SSR ○ サーバ上で React コンポーネントをレンダーできる 7

Slide 8

Slide 8 text

今までの何が違うの? ● そう疑問を抱いている方も多いのでは ● しかし実は... ○ いくつもの問題を解決する ○ 優れた開発体験をもたらす ● 従来技術への回帰、ではない 8

Slide 9

Slide 9 text

このトークの狙い ● RSC への誤解や疑問を解き明かす ○ どんなことができるのか ○ 従来の技術とどう違うのか ○ どう開発スタイルが変わるのか ● RSC に向き合うための知識を手に入れよう 9

Slide 10

Slide 10 text

目次 第1部: SPA と SSR 👈 NEXT 第2部: SSR が抱える問題 第3部: RSC の誕生 第4部: 従来の技術との違い 第5部: 開発スタイルがどう変わるか 10

Slide 11

Slide 11 text

SPA と SSR ● RSC の話に入る前に... ● まず SPA と SSR のおさらいから 11

Slide 12

Slide 12 text

Single-page Application (SPA) ● 単一のページでリッチな Web アプリを作る手法 ○ fetch API で動的にサーバからデータを取得 ○ 取得したデータを React などで描画 ○ JavaScript で画面遷移を制御 ● すべて JavaScript で制御することで... ○ リッチなユーザ体験を実現する 12

Slide 13

Slide 13 text

図: SPA のレンダリングフロー 13

Slide 14

Slide 14 text

SPA が抱える問題 ● コンテンツが出るまで遅い ○ ページアクセス直後は真っ白 ○ JS が実行され始めて、ようやく出る ● クローラーとの相性が悪い ○ 素朴なクローラーだと、空のページと解釈される ■ Google や Bing なら問題ないけど... ○ X で og:image が出なかったり 14

Slide 15

Slide 15 text

Server-side Rendering (SSR) 15 ● サーバ上でコンポーネントをレンダーする技術 ● SPA の問題を解決する

Slide 16

Slide 16 text

図: SPA (SSR有り) のレンダリングフロー 16

Slide 17

Slide 17 text

SSR の有無で比較するとこんな感じ 17 SPA (SSR 有り) SPA (SSR 無し)

Slide 18

Slide 18 text

Hydration とは? ● サーバから受け取った HTML には... ○ まだイベントリスナが設定されてない ○ クリックしても、onClick={handleClick} が発火しない ● そのイベントリスナを登録する作業が「Hydration」 18

Slide 19

Slide 19 text

Hydration とは? ● カピカピの HTML に水を与えて、もとに戻す ● だから Hydration (水和) 19 @HirokiOmote さんの記事の Hydration のたとえを参考に描きました。 https://www.estie.jp/blog/entry/2024/08/05/183235

Slide 20

Slide 20 text

目次 第1部: SPA と SSR 第2部: SSR が抱える問題 👈 NEXT 第3部: RSC の誕生 第4部: 従来の技術との違い 第5部: 開発スタイルがどう変わるか 20

Slide 21

Slide 21 text

SSR が抱える課題 ● いくつか課題がある ○ 不必要な bundle size の増大 ○ データ取得が煩雑 ● Next.js で実装した記事詳細ページを例に解説 21

Slide 22

Slide 22 text

記事詳細ページのソースコード 22

Slide 23

Slide 23 text

不必要な bundle size の増大 ● は、ブラウザ向けに bundle される ○ Hydration や、ブラウザ上でのレンダーに必要なので ● の依存も bundle 対象 ○ marked, sanitize-html ■ marked: 11.2 kB (gzipped) ■ sanitize-html: 80.8 kB (gzipped) ● bundle size が大きくなる 23

Slide 24

Slide 24 text

不必要な bundle size の増大 ● ところで は... ○ ユーザ操作などによって変化しない ○ static なので、サーバでレンダーして終わりで良い ○ Hydration も不要なはず ● 技術的には、bundle に含めないようにできるはず ○ しかし SSR は、問答無用で bundle に含めてしまう 24

Slide 25

Slide 25 text

データ取得が煩雑 ● getServerSideProps でDBからデータを引いて... ○ ページコンポーネントのpropsに渡す ● けど本当は... ○ ページコンポーネントから DB に直接アクセスしたい 25

Slide 26

Slide 26 text

しかし、これはできない ● ./db.js がブラウザの bundle に含まれてしまう ○ ブラウザでは実行できず、エラーになる ● 細かい話だけど... ○ そもそもコンポーネントで async/await 使えない (*1) 26 *1: 厳密には使えるが非推奨 (参考: https://github.com/reactjs/rfcs/pull/229)

Slide 27

Slide 27 text

目次 第1部: SPA と SSR 第2部: SSR が抱える問題 第3部: RSC の誕生 👈 NEXT 第4部: 従来の技術との違い 第5部: 開発スタイルがどう変わるか 27

Slide 28

Slide 28 text

React Server Components (RSC, SC) ● こうした問題を解決するために誕生 ● サーバーで"のみ"レンダーされるコンポーネント ● 特徴 ○ サーバーでしか実行できないコードが書ける ○ ブラウザの bundle に含まれない ■ bundle size が削減 ○ async/await が使える 28

Slide 29

Slide 29 text

RSC を記事詳細ページで使ってみると... 29 書きたかったコードがそのまま書ける!

Slide 30

Slide 30 text

Client Components (CC) ● Server Components の対になるもの ● その正体は... ○ ブラウザでレンダーしていた従来のコンポーネント ● Server Components 登場によって... ○ 名前の整理が行われただけ ○ 今まで書いてたやつが、Client Components と呼ばれるように 30

Slide 31

Slide 31 text

'use client' ディレクティブ ● RSC の世界では、デフォルトで SCになる (*2) ● CC にするには、 ‘use client’ を付ける 31 *2: 厳密には何もつけないと SC / CC のどちらでもない、未確定の状態になる。 コンポーネント利用側に応じて SC / CC どちらになるか、後から決まる。

Slide 32

Slide 32 text

SC と CC の違い 32 Server Components Client Components ブラウザの bundle 含まれない 含まれる async/await ✅ 使える ❌ 使えない (*1) イベントリスナ ❌ 登録できない ✅ 登録できる サーバ専用 API ✅ 使える ❌ 使えない ブラウザ専用 API ❌ 使えない ✅ 使える React Hooks ❌ 使えない ✅ 使える *1: 厳密には使えるが非推奨 (参考: https://github.com/reactjs/rfcs/pull/229)

Slide 33

Slide 33 text

Client Components は無くならない ● 従来の CC だけあった世界に、SC が加わるだけ ○ できることが増える ● ブラウザ上でないとできないことは、CC でやる ○ SC / CC は共存するもの 33 https://github.com/reactwg/server-components/discussions/4 より引用

Slide 34

Slide 34 text

SC / CC は入れ子にできる 34 右図は https://www.plasmic.app/blog/how-react-server-components-work より引用

Slide 35

Slide 35 text

SC から CC にデータを props で渡せる 35 ● シリアライズ可能なものなら OK (*3) ● シリアライズは React が自動でやってくれる ○ シームレスに SC と CC を接続できる *3 厳密にはシリアライズ不可能だが渡せるもの (Promise や Server Actions) がある。

Slide 36

Slide 36 text

RSC と組み合わせて使える機能 ● RSC と組み合わせて使える機能がある ○ Server Actions ○ Taint API ● RSC と併用すると... ○ より優れた開発体験が得られる 36

Slide 37

Slide 37 text

Server Actions ● 聞いたことある人も多いハズ ● サーバで実行される関数を定義できる 37

Slide 38

Slide 38 text

例: ノート作成フォーム 38 CC の中に サーバで実行 される関数を定義できる

Slide 39

Slide 39 text

SC の中で定義して、CC に渡せる ● CC に prop で渡せる ● 本来 CC にはシリアライズ可能なものしか渡せないが... ○ Server Actions は特別に許可されてる ● Server Actions は RSC と密に結合されてる ○ 非常に柔軟なコードが書ける 39

Slide 40

Slide 40 text

Taint API ● 誤って CC に機密情報を渡してしまうのを防ぐ機能 40

Slide 41

Slide 41 text

前提知識 ● SC から CC に渡した props はユーザから丸見え!!! ○ props が丸ごとシリアライズされ、HTML に埋め込まれてる 41 コードは以下の記事を参考にしつつ、改変してます。 https://zenn.dev/cybozu_frontend/articles/react-taint-apis

Slide 42

Slide 42 text

そこで Taint API 42 ● 任意の値を汚染 (taint) できる ● 汚染された値を CC に渡すと... ○ 実行時エラーが発生する ● 誤って CC に機密情報を渡してしまうのを防げる

Slide 43

Slide 43 text

Taint API を使ってみる 43 コードは以下の記事を参考にしつつ、改変してます。 https://zenn.dev/cybozu_frontend/articles/react-taint-apis user.pass を汚染 (taint) する

Slide 44

Slide 44 text

改めて: RSC でできること ● サーバでのみレンダーされるコンポーネントを作れる ● CC でできなかったことが、できるように ○ サーバでのみ動くコードを書ける ○ bundle size 削減, async/await 使える ● Server Actions や Taint API と組み合わせると... ○ 柔軟なコードを書けたり、よくある間違いを防げたり 44

Slide 45

Slide 45 text

目次 第1部: SPA と SSR 第2部: SSR が抱える問題 第3部: RSC の誕生 第4部: 従来の技術との違い 👈 NEXT 第5部: 開発スタイルがどう変わるか 45

Slide 46

Slide 46 text

従来の技術との違い ● RSC がどんなものか分かってきたけど... ● 従来の技術と何が違う? ○ 従来から進んでる? 戻ってる? ● こうした疑問を解き明かしていきます 46

Slide 47

Slide 47 text

よく比較されるもの ● テンプレート系 ○ *.erb (Ruby), *.tmpl (Go) を書いて、それをサーバでレンダー ○ *.php とかも ● SSR 47

Slide 48

Slide 48 text

テンプレート系 ● サーバー上でレンダーされる点は同じ ● ただし... ○ テンプレートをクライアントでレンダーできない ○ サーバ"専用の"テンプレートであるため ● クライアントでレンダーしたかったら、JS を使う 48

Slide 49

Slide 49 text

49 例: erb で Counter を作る

Slide 50

Slide 50 text

erb + JavaScript のイマイチな点 ① 50 別々の技術 (erb/JavaScript)。 キャッチアップコストが掛かる。

Slide 51

Slide 51 text

51 データ受け渡しが複雑。 data 属性を経由する必要あり。 erb + JavaScript のイマイチな点 ② 「+」で文字列から数値に変換する必要あり

Slide 52

Slide 52 text

52 サーバから返ってきた HTML には Counter の中身がない。 コンテンツが出るまで遅延がある、 クローラとの相性が悪いといった 問題がある。 erb + JavaScript のイマイチな点 ③

Slide 53

Slide 53 text

RSC の場合 ● サーバもクライアントも React で書けるから... ○ 同じやり方でどっちもいける ○ データの受け渡しも自然 ○ Counter をレンダーした状態でサーバから返せる ● どっちも React で書けるからこその強み 53

Slide 54

Slide 54 text

SSR ● よく比較されるけど...全く別物 ○ SSR: Client Components をサーバでレンダー ○ RSC: Server Components そのもの ○ レンダー手法 ⇔ 特定の種類のコンポーネント 54

Slide 55

Slide 55 text

RSC を使っていても、SSR できる ● SC と CC が混在するツリーに対し... ○ CC 部分を SSR する、ことは可能 ■ 右図の青部分が SSR される ● RSC 利用時も SSR は推奨 ○ Next.js はデフォルトでそういう挙動 ○ RSC と併用していくもの 55 画像は https://www.plasmic.app/blog/how-react-server-components-work より引用

Slide 56

Slide 56 text

目次 第1部: SPA と SSR 第2部: SSR が抱える問題 第3部: RSC の誕生 第4部: 従来の技術との違い 第5部: 開発スタイルがどう変わるか 👈 NEXT 56

Slide 57

Slide 57 text

開発スタイルがどう変わるか ● 実際に RSC を導入していく場合 ○ 開発スタイルも以前とは変わってくる ● 具体的にどう変わるのか 57

Slide 58

Slide 58 text

変わること ①: data fetch の仕方 ● 今までは... ○ getServerSideProps などから data fetch してた ● これからは... ○ RSC から直接 data fetch する 58

Slide 59

Slide 59 text

data fetch (今まで) 59 コンポーネントと data fetch を分けて書いていた

Slide 60

Slide 60 text

data fetch (これから) 60 コンポーネントと data fetch を分けずに、簡潔に書ける

Slide 61

Slide 61 text

とはいえ ● 普通は直接 ORM 叩かない ○ やりたいならやれば良いけど... ● クリーンアーキテクチャとか導入すると良い 61

Slide 62

Slide 62 text

data fetch (これから) (その2) 62

Slide 63

Slide 63 text

結局のところ... ● data fetch のコードを書く場所は変わったけど... ○ getServerSideProps => RSC ● data fetch コードの抽象化の仕方は変わっていない ○ 今まで通りやればよい 63

Slide 64

Slide 64 text

変わること ②: SC / CC を使い分ける ● 今までは、全部 CC だった ● これからは... ○ SC にできるところは SC に ■ bundle size を減らせるので ● 面倒に感じるかもしれないが... ○ ディレクティブを付けないところは SC になる (*2) ○ interactive にしたいところに 'use client' を付ければ OK 64 *2: 厳密には何もつけないと SC / CC のどちらでもない、未確定の状態になる。 コンポーネント利用側に応じて SC / CC どちらになるか、後から決まる。

Slide 65

Slide 65 text

65 まとめ

Slide 66

Slide 66 text

まとめ: RSC とは何か ● RSC とは ○ サーバだけで動くコンポーネント ● RSC を使うと... ○ サーバーでしか実行できないコードが書ける ○ async/await が使える ○ bundle size を削減できる 66

Slide 67

Slide 67 text

まとめ: 変わること・変わらないこと ● 従来の技術から変わってないこと ○ サーバーでレンダーされる ○ data fetch コードの抽象化の仕方 ● 変わったことも ○ サーバー・クライアント両方を同じ技術 (React) で書ける ■ 学習コスト軽減, データの受け渡しが自然に ○ Server Actions や Taint API が使える ○ CC だけ使う => SC / CC を使い分ける 67

Slide 68

Slide 68 text

まとめ: 振り子ではなく螺旋 ● 従来技術への単なる回帰、ではない ○ 振り子ではなく螺旋の変化が起きてる ● 正しく向き合っていこう 68

Slide 69

Slide 69 text

69 付録

Slide 70

Slide 70 text

ディレクティブを何もつけない場合は? ● SC / CC のどちらでもない、未確定の状態になる ● import 元に応じて、後から決まる ○ SC から import された時: SC になる ○ CC から import された時: CC になる ● SC /CC どちらとしても使えるコンポーネントを作れる ○ Shared Components と呼ばれる 70

Slide 71

Slide 71 text

SC のためのディレクティブは? ● 存在しない ● ‘use server’ は Server Actions のためのもの ● SC にするには... ○ FW が SC だと決め打ちしてるファイルに書く ■ Next.js なら layout.tsx, page.tsx ○ もしくは、SC から import する ● 若干ややこしい 71

Slide 72

Slide 72 text

Server Actions の仕組み ● bundler がサーバ・クライアントのコードを分離する ○ 'use server' 部分: サーバ向けの bundle ○ 'use client' 部分: クライアント (ブラウザ) 向けの bundle ● action={...} は サーバに POST するコードに置換 72

Slide 73

Slide 73 text

RSC で他にできること ● 他にも色々とできることがある ○ 段階的なレンダーと Streaming ■ を境界にして、ツリーの一部分だけレンダー ■ 残りは後からレンダーして、クライアントに Streaming ○ ビルド時に SC をレンダーできる ■ Hydration 無しで SSG 相当のことが可能 ● 詳しくは RFC を参照 ○ https://github.com/reactjs/rfcs/blob/main/text/0188-se rver-components.md 73

Slide 74

Slide 74 text

Islands Architecture ● コンポーネントツリーの一部だけを hydration する技術 ● 名前は interactive 部分が島に見えることから 74 画像は https://jasonformat.com/islands-architecture/ より引用

Slide 75

Slide 75 text

Islands Architecture ● SC/CC と非常によく似ている ● ページを static 部分、interactive 部分に分割できる ○ static 部分は hydration されない ○ ネストもできる ● 本質的には、Islands Architecture と SC/CC は同じ! ○ 同じことができる ○ では SC/CC の存在意義は何なのか 75

Slide 76

Slide 76 text

Islands Architecture に対する SC/CC の存在意義 ● React 世界における、Islands Architecture の標準化 ○ …だと僕は思っている ● Islands Architecture は FW ごとにルールがバラバラ ○ Fresh: islands/*.ts が interactive ○ Astro: client:* ディレクティブつけたものが interactive ● ルールがバラバラだと... ○ FW ごとに使い方を覚えないといけない ○ どの FW でも動く interactive component を作るのが困難 76

Slide 77

Slide 77 text

● 「React ならこのやり方で!」を決めたのが SC/CC ● 標準化されることで... ○ どの FW でも (React ベースなら) 同じやり方で OK ○ Server Component 向けのライブラリが作れる ● 標準規格の上に、追加の機能を設けられる ○ Server Action, Taint API ○ async/await 77 Islands Architecture に対する SC/CC の存在意義

Slide 78

Slide 78 text

GraphQL は RSC で使える? ● YES ● GraphQL は data fetch の一手段でしかない ● 主な data fetch の手段 ○ fetch(...) ○ db.user.get(...) ○ fetchQuery(...) (GraphQL) ● db.user.get(...) を fetchQuery(...) に置き換えたら OK 78

Slide 79

Slide 79 text

data fetch に GraphQL を使う利点はあるか ● まとめてデータを取得できる ○ ユーザ情報 + 記事一覧 + 記事ランキング ○ 1回のリクエストで複数のデータを取れる ● fetch (...) を複数回呼んで data fetch するのと比べて... ○ 総通信時間が減る ○ "理論上は" GraphQL のほうが速い 79

Slide 80

Slide 80 text

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

Slide 81

Slide 81 text

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 より

Slide 82

Slide 82 text

どのフレームワーク使うと良い? ● RSC サポートしてる Production Ready な FW は... ○ Next.js (App Router) しかない ● RSC 使いたいなら... ○ Next.js App Router 使うしかない 82

Slide 83

Slide 83 text

どのフレームワーク使うと良い? ● けどフィットするかは、プロダクトによると思う ○ 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

Slide 84

Slide 84 text

どのフレームワーク使うと良い? ● そこが気に入らなければ... ○ 他のフレームワークを検討しよう ● 例えば Remix とか ○ Next.js と大体同じことができる ○ RSC はサポートされていないが、サポートの計画はある ○ RSC サポートされたら多少書き方変わると思うけど... ■ いざとなったらガッツを出して移行しよう 84

Slide 85

Slide 85 text

RSC を使うと機密情報を 漏洩しやすくなる? ● RSC 登場によって... ○ サーバとクライアントのコードがツリーに混在するように ● 以前より機密情報を漏洩しやすくなっている? 85

Slide 86

Slide 86 text

RSC 時代の漏洩ポイント ● RSC 時代の主な漏洩ポイントは、以下 2 つ ○ CC のソースコード ■ ブラウザの bundle に含まれるため (P23 参照) ○ SC から CC に渡した props ■ HTML にシリアライズされるため (P41 参照) 86

Slide 87

Slide 87 text

RSC 登場以前の漏洩ポイント ● 実は RSC 登場以前も、似たような漏洩ポイントがあった ○ CC のソースコード ■ RSC 登場以前は全て CC だから...つまり全てのコンポーネント! ○ getServerSideProps から返した pageProps (Next.js) ■ Remix の loader でも同じ ○ _buildManifest.js (Next.js) ■ 全ページのパスとサブリソース (JS, CSS) の URL が載ってる ■ ページを IP アドレス制限していても丸見えになってる! 87

Slide 88

Slide 88 text

漏洩リスクの変化 ● 以前のほうが漏洩ポイントは多い ○ そういう点では、RSC 時代は改善してる ● CC や、CC に渡す props に機密情報を含めなければ OK ○ けど、CC かどうか一目で分かりづらい ■ 'use client' が付いてるものは CC だけど... ■ 付いてないものは SC にも CC にもなりうる (P70 参照) ● そもそも RSC ではサーバ専用 API が気軽に使えるので... ○ 機密情報を扱う機会も増えてる ○ 一概に RSC 時代のほうが安全、とも言えない 88

Slide 89

Slide 89 text

機密情報の漏洩への対策 ● まず漏洩リスクを意識しながらコードを書こう ○ 当たり前だけど大事 ● その上で... ○ 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