Featured image of post React Server Components:ウェブレンダリングの未来

React Server Components:ウェブレンダリングの未来

React Server Componentsのアーキテクチャ、サーバー/クライアントの境界、ストリーミングSSR、データフェッチパターン、Suspense統合、既存プロジェクトへの導入戦略を詳しく解説。

React Server Components(RSC)は、Reactアプリケーションのレンダリング方法にパラダイムシフトをもたらしました。コンポーネントはサーバー上でのみ実行され、クライアントに送信されるJavaScriptはゼロバイトです。これは従来のSSRとは異なります。SSRもサーバーでレンダリングしますが、ハイドレーションのためにすべてのJavaScriptをクライアントに送信します。RSCは特別なシリアライズ形式(RSCペイロード)を生成し、クライアント上のReactがコンポーネントコードを実行せずにツリーを再構築します。

最大のメリットはクライアントサイドJavaScriptの劇的な削減です。レイアウトコンポーネント、データフェッチロジック、インタラクションが不要な表示要素はすべてサーバー上で実行されます。バンドルが小さくなることで、ページ読み込みの高速化、メモリ使用量の削減、Core Web Vitalsの改善が実現します。

サーバーとクライアントの境界

'use client''use server'ディレクティブが境界を定義します。ディレクティブがないコンポーネントは、RSCをサポートするフレームワーク(Next.js App Router、Hydrogenなど)ではデフォルトでサーバーコンポーネントになります。

// デフォルトでサーバーコンポーネント
import { db } from "@/lib/db";

export default async function ProductList() {
  const products = await db.query("SELECT * FROM products");
  return (
    <ul>
      {products.map((p) => (
        <li key={p.id}>{p.name}</li>
      ))}
    </ul>
  );
}
// クライアントコンポーネント
"use client";

import { useState } from "react";

export default function LikeButton({ productId }) {
  const [liked, setLiked] = useState(false);
  return (
    <button onClick={() => setLiked(!liked)}>
      {liked ? "♥" : "♡"}
    </button>
  );
}

重要なルール:サーバーコンポーネントはstate、effects、ブラウザAPIを使用できません。サーバーからクライアントに渡すpropsはシリアライズ可能である必要があります(関数、クラスインスタンス、循環参照は不可)。クライアントコンポーネントはコンポジションを通じてサーバーコンポーネントをインポートできますが(子要素やpropsとして渡す)、直接インポートすることはできません。


ストリーミングSSRとSuspense

RSCは標準でストリーミングを可能にします。Suspense境界ごとにデータの準備ができた時点で個別にストリーミングされ、ページ全体のレンダリング完了を待つ必要がありません。

export default function Page() {
  return (
    <div>
      <Header />
      <Suspense fallback={<ProductSkeleton />}>
        <ProductList />
      </Suspense>
      <Suspense fallback={<ReviewSkeleton />}>
        <Reviews />
      </Suspense>
    </div>
  );
}

この例では、Headerが即座にレンダリングされ、ProductListReviewsは各データベースクエリの完了後に順次レンダリングされます。ブラウザはコンテンツを段階的に表示するため、TTFB(初回バイト到達時間)とLCP(Largest Contentful Paint)が大幅に改善します。

指標従来のSSRストリーミングRSC
TTFB全ページ完了後最初のSuspense境界完了後
LCPハイドレーション後最初のストリームチャンク後
JSバンドルアプリ全体クライアントコンポーネントのみ
インタラクティブまで全ハイドレーション後ページごとに早期に

データフェッチパターン

RSCの最も変革的な機能は、APIレイヤーを介さずにコンポーネント内で直接データベースクエリを実行できることです。データを使用する場所でクエリを記述するため、従来のクライアントサイドデータフェッチで問題となっていたウォーターフォールが解消されます。

async function ProductDetails({ id }) {
  const product = await db.query(
    "SELECT * FROM products WHERE id = $1",
    [id]
  );

  const reviews = await db.query(
    "SELECT * FROM reviews WHERE product_id = $1",
    [id]
  );

  return (
    <div>
      <h1>{product.name}</h1>
      <p>{product.description}</p>
      <ReviewList reviews={reviews} />
    </div>
  );
}

Reactはレンダリング中のfetch()呼び出しを自動的に重複排除します。React.cache()関数はこの重複排除をデータベースクエリを含む任意の非同期処理に拡張します。再検証戦略としては、時間ベース(ISR)、オンデマンド(webhook)、ミューテーション発動型の3つが利用可能です。


Server Actionsによるミューテーション

'use server'ディレクティブで定義されるServer Actionsは、APIルートを構築せずにフォーム送信とデータミューテーションを処理します。

async function createProduct(formData) {
  "use server";

  const name = formData.get("name");
  const price = formData.get("price");

  await db.query(
    "INSERT INTO products (name, price) VALUES ($1, $2)",
    [name, price]
  );

  revalidatePath("/products");
}

export default function ProductForm() {
  return (
    <form action={createProduct}>
      <input name="name" required />
      <input name="price" type="number" required />
      <button type="submit">作成</button>
    </form>
  );
}

Server Actionsはプログレッシブエンハンスメントをサポートしており、JavaScriptがなくてもフォームは動作します。JavaScriptが利用可能な場合はfetch経由で送信され、サーバーは影響を受けるコンポーネントのみを再レンダリングします。エラーハンドリングと楽観的更新はReact 19のuseActionStateなどのクライアントフックで管理します。


既存プロジェクトへの導入戦略

Next.jsのPages RouterからApp Routerへの移行は段階的に行えます。移行期間中は両方のルーターを並行して運用可能です。

  1. データコンポーネントから始める: 静的なページやデータ量の多いページをRSCに移行
  2. クライアント境界を特定: state、effects、イベントハンドラを使用するコンポーネントを'use client'
  3. インタラクティブな島を抽出: クライアントコンポーネントは小さく末端に保つ
  4. データフェッチを上位に移動: 親サーバーコンポーネントでデータを取得し、propsとして子に渡す

すべてのアプリケーションがRSCの恩恵を受けられるわけではありません。高度にインタラクティブなダッシュボード、リアルタイムコラボレーションツール、ほとんどのコンポーネントがstateやeffectsを使用するアプリケーションでは、従来のクライアントサイドレンダリングやSSRの方が適切な場合があります。


パフォーマンスとバンドルへの影響

RSCの最も測定可能な影響はJavaScriptバンドルの削減です。従来クライアントにJavaScriptとして送信されていたレイアウトコンポーネントやデータフェッチロジックが、サーバー上でのみ実行されるようになります。RSCペイロードはコンパクトなバイナリ形式で、クライアント上のReactが効率的に処理できます。

ネットワークタブでの分析では、RSCレスポンスは特殊なコンテンツタイプのストリームチャンクとして表示されます。各チャンクは解決されたSuspense境界に対応し、ブラウザはコンテンツを段階的に受信・表示するため、JavaScriptバンドルのサイズ測定だけでは評価できない知覚パフォーマンスの向上が得られます。

現在の状況と展望

2024年時点で、React Server Componentsは本番環境での採用が定着しました。Next.js 14はRSCを中核とするApp Routerを安定版として提供し、ReactチームはRSC互換の状態管理ソリューションやツールの改善を進めています。Hydrogen(Shopify)やRedwoodJSなどの新しいフレームワークもRSCを主要なレンダリングモデルとして採用しています。

学習曲線やサーバー/クライアント境界の設計判断に課題はありますが、コンテンツリッチなサイト、Eコマースプラットフォーム、ページ読み込みパフォーマンスが重要なアプリケーションでは、JavaScript削減とストリーミング改善のメリットが学習コストを大きく上回ります。