4月14日、Manuel Schiller、Tanner Linsley、Jack Herringtonが「React Server Components Your Way」と題した記事を公開した。この記事では、TanStack Startが採用するReact Server Componentsの革新的なアプローチについて詳しく紹介されている。以下に、その内容を紹介する。
React Server Components(RSC)は、Reactチームが2020年に発表した技術で、コンポーネントをサーバー側でレンダリングしてクライアントに送信することで、バンドルサイズとパフォーマンスの改善を目指している。しかし、現在主流のNext.js App Routerのようなアプローチでは、新たな課題が生まれている。
従来のRSCアプローチの根本的問題
Next.jsをはじめとする多くの実装では、サーバーがコンポーネントツリー全体を所有し、フレームワークの規約がアプリケーション全体の構造を決定する仕組みになっている。この「サーバー主導」モデルの問題点を著者らは次のように指摘する:
- RSCを使うには最初からそのパラダイム全体を採用する必要がある
- 既存のクライアントサイドツールとの統合が困難
- フレームワークが強制する設計に従わざるを得ない
TanStackチームが着目したのは、「RSCから価値を得るために、本当にアプリケーション全体をサーバー主導にする必要があるのか?」という疑問だった。
発想の転換:RSCは「ただのデータストリーム」
TanStack Startの核となるアイデアは、RSCを「ただのデータストリーム」として扱うことだ。サーバー所有のコンポーネントツリーではなく、クライアント側で必要に応じて取得、キャッシュ、レンダリングできる普通のデータとして位置づけている。
この発想により、RSCはReact Flightストリームとして素直に動作し、特別なルールやAPIに包まれることなく既存ツールと即座に互換性を持つ。
実装例:意図的にシンプルなAPI
TanStack StartでのRSC実装では、APIサーフェスを極限まで絞っている:
import { createServerFn } from '@tanstack/react-start'
import {
createFromReadableStream,
renderToReadableStream,
} from '@tanstack/react-start/rsc'
// サーバー関数を作成
const getGreeting = createServerFn().handler(async () => {
// RSCの読み取り可能ストリームを作成
return renderToReadableStream(
<h1>Hello from the server</h1>
)
})
function Greeting() {
const query = useQuery({
queryKey: ['greeting'],
queryFn: async () =>
// ストリームからレンダリング可能な要素を作成
createFromReadableStream(
await getGreeting()
),
})
return <>{query.data}</>
}
核となるAPIはわずか3つ:
- renderToReadableStream: サーバー側でReact要素をFlightストリームにレンダリング
- createFromReadableStream: クライアント側またはSSR中にFlightストリームをデコード
- createFromFetch: より便利な場合にfetchレスポンスから直接デコード
既存ツールとの自然な統合が生む威力
RSCを「ただのデータ」として扱うことで、キャッシュ戦略が劇的に簡素化される。TanStack Queryとの組み合わせでは、特別な「RSCモード」は不要で、従来のクライアントサイドクエリと全く同じように扱える:
function PostPage({ postId }: { postId: string }) {
const { data } = useSuspenseQuery({
queryKey: ['greeting-rsc', postId],
queryFn: async () => ({
Greeting: await createFromReadableStream(await getGreeting()),
}),
staleTime: 5 * 60 * 1000, // 静的コンテンツならInfinity
})
return <>{data.Greeting}</>
}
明示的なキャッシュキー、staleTime、バックグラウンドリフェッチなどの既存機能がそのまま使える。TanStack Routerでも、RSCペイロードは「ただのデータ」としてルートローダーで自然に動作し、ルーターキャッシュの恩恵を受けられる。
セキュリティファーストの設計思想
TanStack Startは意図的に**'use server'アクション**をサポートしていない。既知の攻撃ベクトルや暗黙的なネットワーク境界の問題を回避するためだ。代わりに、createServerFnによる明示的なRPC(Remote Procedure Call)を要求し、強化されたシリアル化、バリデーション、ミドルウェアセマンティクスを提供している。
実際の性能改善:具体的な数値で証明
TanStackチームは自社サイト(tanstack.com)のコンテンツ重要部分をRSCに移行し、印象的な改善データを取得した:
転送サイズの削減:
- ブログ投稿ページ: 153 KB gzipped削減
- ドキュメントページ: 153 KB gzipped削減
- ドキュメント例ページ: 40 KB gzipped削減
Lighthouseスコアの向上:
/blog/react-server-componentsが52→74に改善- Total Blocking Timeが1,200ms→260msに短縮
- 転送サイズが1,101 KiB→785 KiBに削減
最も重要な改善は、「重いクライアント側の処理がクライアントに送られなくなった」ことだ。Markdownパーサーやシンタックスハイライターなどがサーバー側に移動し、ブラウザの負荷が大幅に軽減された。
ただし著者らは、RSCの効果には明確な向き不向きがあることも正直に報告している。コンテンツ重要・依存関係の多いページ(ドキュメント、ブログ、複雑なMarkdownパイプラインなど)では劇的な効果があるが、既にクライアント状態と相互作用が主体のページ(ダッシュボード、ビルダー、長時間のアプリセッション)では効果が限定的だった。
全スペクトラム対応:選択の自由を重視
TanStack Startの哲学は「適材適所」だ。単一のフレームワークで以下すべてのユースケースをカバーする:
- 完全インタラクティブ: RSCを使わないクライアント主体のSPA
- ハイブリッド: 静的シェルはサーバーコンポーネント、インタラクティブ部分はクライアントコンポーネント
- ほぼ静的: 主に静的コンテンツをRSCでレンダリング、必要な部分のみクライアント側で対応
- 完全静的: ビルド時にすべてを事前レンダリング
ルート単位、コンポーネント単位、ユースケース単位でパターンを自由に選択できる。RSCは必要に応じて追加する最適化手段であり、アプリケーション全体を支配するパラダイムではない——この哲学が、Next.jsの重厚なサーバー主導アプローチとの最大の違いといえるだろう。
詳細はReact Server Components Your Wayを参照していただきたい。