- Published on
React Server Components と Next.js App Router 完全攻略 — RSCプロトコル・Server Actions・PPR・Streaming (2025)
- Authors

- Name
- Youngju Kim
- @fjvbn20031
"The future of React is not about rendering faster. It's about rendering less." — Dan Abramov (RSC発表、2020年12月)
2020年12月、Dan AbramovとLauren Tanが "React Server Components" 発表動画を公開した。当時多くの開発者は「また新しいの学ぶのか」と反応した。2025年、RSCはNext.js 14/15のデフォルトとなり、Remix、TanStack Start、RedwoodJSがRSCを取り込もうとしている。React史上最大のパラダイム転換だ。
しかしRSCは複雑。「なぜ'use server'と'use client'が必要なのか?」「SSRと何が違う?」「いつどこで何がレンダされる?」本稿はその地図。
1. Reactレンダリング史 — 4段階
1. CSR (2013–2015)
create-react-app 時代: サーバは空のHTML + JSバンドルを送り、ブラウザがJSを実行してDOMを生成。First Paint遅い、SEO弱い、ネットワークウォーターフォール。
2. SSR (2017+)
Next.jsなどが ReactDOMServer.renderToString でサーバ側HTML生成、ブラウザで hydrate。FCPは速いがクライアントバンドルは変わらず、TTIはむしろ悪化、getServerSideProps ウォーターフォールの悩み。
3. SSG / ISR (2019+)
ビルド時または周期的にHTML生成。Vercel・Netlifyの全盛期。動的コンテンツに弱い。
4. RSC (2020発表、2022実用化)
サーバでのみレンダされるコンポーネント。クライアントにJSは無い。
// サーバ専用、バンドルに入らない
async function ProductList() {
const products = await db.query('...') // DB直接アクセス!
return (
<ul>
{products.map(p => <ProductCard key={p.id} product={p} />)}
</ul>
)
}
革命の本質: サーバロジック (DBクエリ、ファイルIO) がReactコンポーネント内に自然に入る。APIレイヤ無しで。
2. Server Component vs Client Component
| 種別 | 実行場所 | バンドル | マーカー |
|---|---|---|---|
| Server Component | サーバのみ | × | デフォルト (App Router) |
| Client Component | サーバ+クライアント | ○ | 'use client' |
| Shared | 両方 | 条件付き | ディレクティブ無し |
"use client"
'use client'
import { useState } from 'react'
export default function Counter() {
const [n, setN] = useState(0)
return <button onClick={() => setN(n+1)}>{n}</button>
}
このファイルとインポートするすべてがクライアントバンドルに入る。"Client Boundary" のマーカー。
"use server"
// actions.ts
'use server'
export async function createPost(formData: FormData) {
await db.posts.insert({ ... })
}
// <form action={createPost}>
Server Action — クライアントから呼ぶがサーバで実行される関数。内部的にはRPCエンドポイントに変換。
境界のルール
- Server → Server: 自由 (関数呼び出し)。
- Server → Client:
childrenまたはserializableなpropsで。 - Client → Client: 通常のReact。
- Client → Server: Server Actionでのみ。
よくある誤解 — 「Server ComponentはSSR」
違う。SSRは「サーバでも一度レンダ」。RSCは「サーバでのみレンダ」。クライアントバンドルに無い。両者は組み合わせ可能だが別の仕組み。
3. RSC Flight Protocol
RSCが生成するのはHTMLではなく、コンポーネントツリーのシリアライズ表現。ナビゲーション時に新しいサーバツリーをマージするため、そして「この位置にこのClient Componentとpropsを付けて」を表現するため。
Flight Format — ストリーミングJSON
1:"$Sreact.suspense"
2:{"children":["$","h1",null,{"children":"Hello"}]}
3:I[{"id":"Counter","chunks":["..."]}]
0:["$","$1",null,{"children":["$","div",null,{"children":["$","$L3",null,{"initial":0}]}]}]
$プレフィクスは特殊値 (コンポーネント、Suspense、参照) 表現。$L3は「Client Component 3番をlazy load」指示。- ストリーミング送信 — 後でresolveされる部分は後で。
利点
- インクリメンタルレンダ — 各チャンクが準備でき次第送信。
- バンドル分割 — 実際に使うClient Componentのチャンクのみロード。
- Hydration最適化 — 既にレンダ済みの部分は再レンダしない。
ベンチマーク
同じページのSSR vs RSC: TTFBは同程度、JSバンドル30–70%削減、TTIはRSCの方が速い (hydrate対象が少ない)。
4. Next.js App Router — コンベンションの言語
ファイルシステムルーティング
app/
layout.tsx
page.tsx
blog/
layout.tsx
page.tsx
[slug]/
page.tsx
loading.tsx
error.tsx
@sidebar/
default.tsx
page.tsx
特殊ファイル
page.tsx— URLにマッチするコンポーネント。layout.tsx— 共有レイアウト、再レンダされない。template.tsx— 毎回再レンダ。loading.tsx— Suspenseで自動ラップ。error.tsx— Error Boundaryで自動ラップ。not-found.tsx— 404。default.tsx— 並列ルーティングのデフォルト。route.ts— APIエンドポイント。
Layout Nesting — 真の利点
ページ間遷移時 layoutは再レンダされない。サイドバーstateが保持される。Pages Routerでは不可能。
並列ルーティング @slot
1画面に複数の独立ルート。ダッシュボードに最適。
Intercepting Routes (..)
写真クリック時モーダルで開く、URLは /photo/123。リロードで全画面。Instagram風UX。
5. データフェッチングの革命
従来 (Pages Router)
getServerSideProps はページ単位のみ → deep componentではprop drillingかグローバルstate。
App Router — どこでもasync可能
async function UserProfile({ userId }) {
const user = await db.users.find(userId)
return <div>{user.name}</div>
}
ツリーのどこでも await。Reactが並列実行。
自動重複排除 (Dedup)
同じ fetch URLを複数コンポーネントで呼んでも実際は1回。React 18の cache() APIでも可能。
ウォーターフォール防止
async function Page() {
const [user, posts] = await Promise.all([getUser(), getPosts()])
return <UI user={user} posts={posts} />
}
独立データは常に並列。
6. Server Actions — fetchの終わり?
基本
'use server'
import { revalidatePath } from 'next/cache'
export async function createPost(formData: FormData) {
await db.posts.insert({ title: formData.get('title') })
revalidatePath('/blog')
}
// <form action={createPost}><input name="title" /><button>Create</button></form>
JavaScript無効でも動く (form → サーバPOST)。Progressive Enhancement。
useActionState — エラーとpending
'use client'
const [state, action, isPending] = useActionState(createPost, null)
<form action={action}>
{state?.error && <p>{state.error}</p>}
<button disabled={isPending}>Submit</button>
</form>
useOptimistic — 即時フィードバック
const [optimisticMsgs, addOptimistic] = useOptimistic(messages,
(state, newMsg) => [...state, { ...newMsg, sending: true }])
Twitter/WhatsApp風UX: ユーザーには即時反映、保存は裏で。
Server Actions vs API Routes
- API Route: RESTエンドポイント、外部からもアクセス可。
- Server Action: 内部コンポーネントからのみ、自動CSRF/トークン防御。
内部ミューテーション → Server Action。外部API → Route Handler。
7. キャッシュ4層 — 最も混乱するところ
- Request Memoization — 1レンダ内の同じ
fetchは1回。Reactレベル。 - Data Cache —
fetch()がデフォルトで永続キャッシュ。{ cache: 'no-store' }や{ next: { revalidate: 60 } }で制御。Next.js 15でデフォルトが no-store に変更。 - Full Route Cache — 静的ルートのHTML + RSCペイロード全体キャッシュ。ビルド時またはISR。
- Router Cache (クライアント) — 訪問したルートのRSCペイロードをメモリキャッシュ。戻る操作が即時。
router.refresh()で手動無効化。
無効化
revalidatePath('/blog')— パス単位。revalidateTag('posts')— タグ単位。- 裏でstale-while-revalidate。
2024–2025 簡素化
Next.js 15「stable cache semantics」: fetchキャッシュは opt-in へ、dynamicIO フラグで明示的静的/動的、実験的 'use cache' ディレクティブで関数単位キャッシュ。
8. StreamingとSuspense
サーバは完成してから一度に送るのではなく、準備でき次第チャンクで送る。
loading.tsx による自動Suspense
export default function Page() {
return (
<>
<Header />
<SlowComponent />
<Footer />
</>
)
}
loading.tsx があれば自動で <Suspense fallback={<Loading />}> でラップ。
手動Suspense境界
<Suspense fallback={<ProductSkeleton />}><SlowProducts /></Suspense>
<Suspense fallback={<ReviewsSkeleton />}><Reviews /></Suspense>
2つが独立にロード。速く終わる方が先に表示。
ストリーミングHTMLの仕組み
<html><head>...を先に送信。- 各Suspense境界がresolveされると
<script>でHTML注入。 - HydrationもSelective Hydrationで順次。
TTFB vs FMP
従来SSR: 全部待ってから送信 (TTFB遅、FMP遅)。ストリーミング: 即座に開始 (TTFB速、FMP速、一部は後)。
9. PPR — Partial Prerendering (Next.js 15)
問題
完全静的は速いが柔軟性無し。完全動的は柔軟だが遅い。現実のページは一部静的、一部動的。
解決 — 1ページに混在
export const experimental_ppr = true
export default function Page() {
return (
<>
<Header /> {/* 静的 */}
<Hero /> {/* 静的 */}
<Suspense fallback={<Skeleton />}>
<DynamicCart /> {/* リクエスト毎に動的 */}
</Suspense>
</>
)
}
仕組み
- ビルド時: 静的部分を事前レンダ → shell。
- リクエスト時: 動的部分のみレンダ → ストリーミングで結合。
- 結果: 静的の速さ + 動的の柔軟性。
現状 (2025)
Next.js 15でexperimental、Vercelで本番運用中、2026年安定化見込み。
10. RSC vs SolidStart vs Qwik vs 他
SolidStart
Solid.jsベース (Reactより速い、reactive primitive)。Islands + RSC風サーバ関数。Viteベース、複雑度控えめ。
Qwik City
Resumability — hydration無し。HTMLにstateをシリアライズ、インタラクション時のみ該当コードをダウンロード。初期JS 1KB未満も可能。
TanStack Start
Tanner Linsley (React Query)。Viteベース、TypeScript-first。2025年beta、RSC計画中。
Remix (現React Router v7)
「Use the Platform」。2024年末にReact Router v7へ統合。RSCサポートは2025年導入。
選択マトリクス
| チーム・目的 | 推奨 |
|---|---|
| Reactに慣れた大規模チーム | Next.js (RSC) |
| 極限パフォーマンス、小規模 | Qwik |
| Solid.js派 | SolidStart |
| 型安全最優先 | TanStack Start |
| Use the Platform派 | React Router v7 |
11. Pages → App Router 移行
共存
同じNext.jsプロジェクトで app/ と pages/ を共存可能。
順序
- 新機能は
app/で。 - API routes は当面維持。
- ページ単位で漸進移行。
_app.tsx→app/layout.tsxに統合。
落とし穴
- CSS —
styles/globals.css二重読み込み。 - middleware — 共有だがAPIに一部差異。
<Image>— 同一。useRouter—next/navigationに変更必須。
12. よくある間違い TOP 10
- どこにでも
'use client'— RSCの利点消滅。 - Server Componentで
useState— TypeScriptが止める。 - Client ComponentでDB直接アクセス — セキュリティ事故、自動ブロック。
- Server Componentで
window— 存在しない。 - 毎リクエストで巨大fetch — キャッシュ戦略必須。
- ネストSuspense無し — 全部一緒にローディング。
- 順次
await— ネットワークウォーターフォール。 - Server Actionで巨大オブジェクト渡し — シリアライズコスト。
revalidatePath漏れ — mutation後にUIが古いまま。- クライアントstateをServer Componentに依存 — 不可能。
13. App Routerチェックリスト
- デフォルトはServer Component —
'use client'は必要時のみ。 - layoutの共有 — 再レンダされない前提を確認。
- Suspense境界 — 遅い部分を独立隔離。
- loading.tsx — ページ別ローディングUI。
- error.tsx — ページ別エラー境界。
- 並列データフェッチ —
Promise.all活用。 - Server Action検証 — zodで入力バリデーション。
- revalidate戦略 — path/tagベース。
- Dynamic/Static明示 —
export const dynamic = 'force-static'。 - TypeScript strict 維持。
- Turbopack dev —
next dev --turbo。 - bundle analysis —
@next/bundle-analyzer。
まとめ — 「サーバは再び重要になった」
RSCは単なる最適化ではない。「フロントエンドとバックエンドの境界をコンポーネントレベルまで引き下ろした」パラダイム転換だ。 10年前「SPAが未来」と叫んだ業界が、今は「サーバが再び重要」と言う。だがこれは過去への回帰ではなく新しい総合だ — サーバのDBアクセスの便利さ + クライアントの相互作用性。
Reactチームの「Use the Platform」哲学は、Web標準 (form、HTMLストリーミング、progressive enhancement) をReactに溶かし込んだものだ。2030年のReactは今とは大きく違うだろう。しかしその方向はすでに見えている: 小さなクライアントバンドル、速い初画面、サーバで解けることはサーバで、複雑さはフレームワークで。
"With Server Components, you don't have to choose between 'it's a rich app' and 'it's fast'. You get both." — Sebastian Markbåge