PaPoo
cover
technews
Author
technews
世界の技術ニュースをリアルタイムでキャッチし、日本語でわかりやすく発信。AI・半導体・スタートアップから規制動向まで、グローバルテックシーンの「今」をお届けします。

「use server」「use client」をなくす発想が面白い。TanStack Start が示す新しい RSC の形

キーポイント

本文

React Server Components(RSC)は、ざっくり言うと「ブラウザではなくサーバー側で動く React コンポーネント」です。
普通の React コンポーネントは JavaScript としてブラウザに送られますが、RSC は 結果だけ がブラウザに届きます。なので、重い処理をブラウザに持ち込まずに済む。これはかなり気持ちいい発想です。

元記事では、この RSC の世界で TanStack Start がかなり変わったアプローチを取っている と紹介しています。
一言でいうと、TanStack Start は RSC を「描画するもの」ではなく「データとして受け渡すもの」​ と捉えている、という話です。ここがいちばん面白いところだと思います。

まず、従来の RSC は「サーバーファースト」寄り

Next.js など、RSC をサポートする多くのフレームワークは、まずサーバー側でページ全体の構造を組み立てます。
そのうえで、ボタンやフォームのようなインタラクティブな部分だけを 'use client' で切り出す、という作りです。

これはわかりやすい反面、アプリ全体が RSC 前提 になりやすい。
つまり、どこで RSC を作るか、どこでクライアントに渡すか、どこを境界にするかを、フレームワークがかなり強く決めてしまうんですね。

個人的には、これは便利な半面、少し“フレームワークに考えさせすぎる”感じもあります。
もちろんそれが良い場面も多いのですが、「自分で境界をちゃんと見たい」という人には息苦しいこともあると思います。


image_0001.png

TanStack Start の考え方は「RSC はデータ」

TanStack Start の発想はかなり独特です。
RSC を HTTP でやりとりするデータ として扱います。ここで出てくるのが React Flight です。

React Flight は、React のコンポーネントツリーをネットワーク越しに運ぶための専用フォーマットです。
元記事では、JSON や HTML と比較して説明されていました。

これ、地味に革命的です。
普通の JSON フェッチだと、日付整形、Markdown 変換、シンタックスハイライトみたいな処理は、最終的にクライアントでも動く JS が必要になります。
でも Flight なら、そういう重い処理をサーバー側だけで済ませやすい。しかも ストリーミング で少しずつ流せるので、全部終わるのを待たずに表示を始められる。これはかなり実用的です。


「どこでも生成できる」「どこでも受け取れる」が強い

TanStack Start の良さは、RSC を特別な聖域に閉じ込めないことです。

image_0002.jpeg

元記事では、次のような特徴が挙げられていました。

ここがかなり TanStack っぽいです。
つまり、「RSC 専用の新しいやり方を覚えてください」ではなく、​いつものデータ取得の延長で扱っていいよ という態度なんですよね。

これはかなり好感が持てます。新しい概念が増えると、それだけで開発者の脳みそは疲れるので……。
「RSC のための特別ルール」を増やさない設計は、長期的に見て強いと思います。


クライアントファーストという割り切り

TanStack Start は、Next.js とは逆に クライアントファースト の構成を取ります。
クライアント側の Router がツリーを持ち、RSC はその中に流し込まれるデータとして扱われます。

元記事の図を言い換えると、

image_0003.png

という感じです。

この違いはかなり大きいです。
TanStack Start では、JSON を fetch するのと同じ感覚で RSC を取得・更新できます。
「RSC だから特別」というより、「いつものデータ層に乗っている」という感覚に近い。

個人的には、この割り切りはかなり現代的だと思います。
アプリの主導権をクライアント側に置きつつ、重い処理だけサーバーに逃がす。実に合理的です。


use server を使わず、createServerFn で境界を明示する

TanStack Start は 'use server' をサポートしていません。
つまり、ファイルの先頭に文字列を置くだけで「ここからサーバーです」とする方式は採らない、ということです。

これには賛否ありそうですが、元記事の指摘はかなり筋が通っています。
'use server' のようなディレクティブは便利な一方で、

image_0004.png

がコードを見ただけでは追いにくいことがあるんですよね。

TanStack Start では、代わりに createServerFn を使います。

import { createServerFn } from '@tanstack/react-start'

export const createPost = createServerFn({ method: 'POST' })
  .validator(schema)
  .handler(async ({ data }) => { ... })

これはかなり親切です。
method が見えるし、validator があるかも見えるし、handler が実処理だと一目でわかる。
つまり、​魔法ではなく設計として境界が読める んです。

私はこういう「コードを読んだ瞬間に挙動がわかる」設計が好きです。
便利さよりも、まず可読性。これは大事だと思います。


createCompositeComponent が合成の肝

RSC の話で難しいのは、サーバーが描いた UI に、どうやってクライアントのボタンやフォームを差し込むか、という点です。
TanStack Start では、ここを Composite Components で解決します。

image_0005.png

これは、サーバー側が UI の骨格を作り、slot のような差し込み口を開けておいて、クライアントがそこへ好きなコンポーネントを流し込める仕組みです。

たとえば、記事では children を slot として使う例が紹介されています。

この構造のいいところは、​サーバーが見た目の責任を持ちつつ、インタラクションはクライアントに任せられる ところです。
しかも、クライアント側がツリーを所有しているので、場合によっては 'use client' が不要になることもある。

ただし、ここで重要なのは「完全に不要」ではないことです。
元記事でも触れられていましたが、サーバーが直接クライアントコンポーネントをツリーに配置するケースでは use client が必要になる場面があります。
つまり TanStack Start は、「ディレクティブを全廃する」のではなく、​必要最小限に抑える という立場ですね。

この姿勢はかなり現実的です。
理想論で「全部なくせる」と言い切らないのが、むしろ信頼できます。


実装編では 14 パターンを紹介

image_0006.png

元記事の後半では、実際のコードを使って TanStack Start の RSC 実装パターンを 14 個紹介しています。
本文で確認できた範囲でも、かなり丁寧に「どう組むか」が説明されています。

セットアップ

RSC を有効にするには、@vitejs/plugin-rsc と TanStack Start の rsc.enabled オプションが必要です。
Vite の設定に組み込んで、RSC を使える状態にします。

また、データ取得には ky、ダミーデータには dummyjson を使っていました。
このあたりは実験用サンプルとしてわかりやすい構成です。


パターン1: 基本描画

renderServerComponent を使って、サーバーコンポーネントを描画し、その結果を Loader 経由でそのまま使うパターンです。

これはいちばん素直で、​​「まず RSC を表示してみたい」​ ときの基本形です。
スロットが不要なら、これでかなりシンプルに書けます。


パターン2: children スロット合成

createCompositeComponent を使って、サーバーの UI 骨格に children を差し込むパターンです。
CounterLikeButton のようなインタラクティブ要素を埋め込めるのがポイントです。

これはかなり実践的です。
「見た目はサーバーで、操作はクライアントで」という分担がきれいにできます。

image_0007.png


パターン3: render props 合成

サーバー側が関数を props として受け取り、クライアント側がデータを受けながら UI を描く形です。
いわゆる render props の考え方を RSC 合成に持ち込んでいます。

ここまで来ると、単なる「画面を返す」ではなく、​UI の組み立て方そのものを設計している 感じがします。
TanStack Start の面白さは、まさにこの“UI 合成の自由度”にあると思います。


この記事を読んで感じること

正直、TanStack Start の RSC にはかなり新鮮さがあります。
React の世界ではどうしても「サーバー中心に寄せるか、クライアント中心に寄せるか」で議論が起きがちですが、TanStack Start は クライアントを主役にしたまま、必要なときだけサーバーの力を使う 方向に振っています。

この考え方は、今のフロントエンド開発にかなり合っている気がします。
アプリは結局、ユーザー操作が中心です。だったら、クライアントを主軸に置くのは自然ですし、重い処理や安全性が必要な部分だけサーバーに逃がすのも理にかなっています。

もちろん、Next.js のようなサーバーファーストの設計が不要になるわけではありません。
むしろ、それぞれ向いている場面が違うのだと思います。
でも TanStack Start の「RSC はデータである」という切り口は、RSC を難しく感じていた人ほど刺さるのではないでしょうか。

image_0008.svg


まとめ

TanStack Start の RSC は、
​「RSC を特別な構文で囲い込む」のではなく、「普通のデータとして扱う」​ のが最大の特徴です。

その結果、

というメリットが生まれます。

まだ発展途上の部分もあるはずですが、設計思想としてはかなり筋がいいと思います。
「RSC をどう使うか」ではなく、「RSC をどうデータ基盤に溶かすか」を考える発想。これは今後かなり面白くなりそうです。


参考: “use server” も “use client” も要らない —TanStack Start が示す新しいRSCの形

同じ著者の記事