Skip to content

✍️ 필사 모드: React Server Components と Next.js App Router 完全攻略 — RSCプロトコル・Server Actions・PPR・Streaming (2025)

日本語
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.

"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エンドポイントに変換。

境界のルール

  1. Server → Server: 自由 (関数呼び出し)。
  2. Server → Client: children またはserializableなpropsで。
  3. Client → Client: 通常のReact。
  4. 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層 — 最も混乱するところ

  1. Request Memoization — 1レンダ内の同じ fetch は1回。Reactレベル。
  2. Data Cachefetch() がデフォルトで永続キャッシュ。{ cache: 'no-store' }{ next: { revalidate: 60 } } で制御。Next.js 15でデフォルトが no-store に変更
  3. Full Route Cache — 静的ルートのHTML + RSCペイロード全体キャッシュ。ビルド時またはISR。
  4. 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>
    </>
  )
}

仕組み

  1. ビルド時: 静的部分を事前レンダ → shell。
  2. リクエスト時: 動的部分のみレンダ → ストリーミングで結合。
  3. 結果: 静的の速さ + 動的の柔軟性。

現状 (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/ を共存可能。

順序

  1. 新機能app/ で。
  2. API routes は当面維持。
  3. ページ単位で漸進移行。
  4. _app.tsxapp/layout.tsx に統合。

落とし穴

  • CSS — styles/globals.css 二重読み込み。
  • middleware — 共有だがAPIに一部差異。
  • <Image> — 同一。
  • useRouternext/navigation に変更必須。

12. よくある間違い TOP 10

  1. どこにでも 'use client' — RSCの利点消滅。
  2. Server Componentで useState — TypeScriptが止める。
  3. Client ComponentでDB直接アクセス — セキュリティ事故、自動ブロック。
  4. Server Componentで window — 存在しない。
  5. 毎リクエストで巨大fetch — キャッシュ戦略必須。
  6. ネストSuspense無し — 全部一緒にローディング。
  7. 順次 await — ネットワークウォーターフォール。
  8. Server Actionで巨大オブジェクト渡し — シリアライズコスト。
  9. revalidatePath 漏れ — mutation後にUIが古いまま。
  10. クライアント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 devnext 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

현재 단락 (1/193)

2020年12月、Dan AbramovとLauren Tanが "React Server Components" 発表動画を公開した。当時多くの開発者は「また新しいの学ぶのか」と反応した。2025...

작성 글자: 0원문 글자: 8,197작성 단락: 0/193