•13 min read

React Server ComponentsとClient Components:徹底解説

React Server ComponentsとClient Components:徹底解説

React Server Components (RSC) の登場は、React アプリケーションのアーキテクチャパラダイムを根本的に変えました。RSC 以前は、React は主にブラウザでのインタラクティブなコンポーネントのレンダリング(または、時折 SSR を介して HTML にプリレンダリングすること)に焦点を当てていました。React Server Components の登場により、サーバー上でのみ実行されるコンポーネントと、クライアント上で実行されるコンポーネントという明確な分割が生まれました。

このガイドでは、このハイブリッドアプローチの核となる違い、アーキテクチャ上の利点、そしてサーバーコンポーネントとクライアントコンポーネントのどちらを使用するかを決定するための意思決定マトリックスについて詳しく説明します。

Audio Briefing
0:00 / 0:00

React Server Components とは?

React Server Components は、サーバー上でのみ実行されるコンポーネントです。これらは JavaScript としてブラウザに送信されることはありません。代わりに、サーバーが React コンポーネントのロジックを実行し、データベースや内部 API から必要なデータを直接フェッチし、結果の HTML/UI をクライアントに直接ストリーミングします。

サーバーコンポーネントの利点

  1. クライアントサイド JavaScript がゼロ: サーバーコンポーネントはブラウザで実行されないため、アプリケーションの JavaScript バンドルに重さを加えることはありません。重いマークダウンパーサーや日付フォーマットライブラリのような大規模なライブラリをサーバーコンポーネント内でインポートしても、ユーザーのネットワーク接続に負担をかけることはありません。
  2. 直接データアクセス: サーバーコンポーネントは信頼できる環境で実行されます。中間 API ルートを立ち上げたり、useEffect のデータフェッチのウォーターフォールに対処したりすることなく、コンポーネントから直接データベースを安全にクエリできます。
  3. 初期ロード時間の改善: サーバーはレンダリングされた UI をクライアントに段階的にストリーミングできるため、ユーザーは大規模な JS バンドルのダウンロードとハイドレーションを待つよりもはるかに速くページを表示し、操作できます。

注意点:インタラクティブ性がない

サーバーコンポーネントはブラウザで実行されないため、ブラウザ専用の API やクライアントサイドの状態に依存する React フックを使用できません。サーバーコンポーネント内では、useState、useEffect、onClick、または window.localStorage を使用できません。

Advertisement

クライアントコンポーネントとは?

クライアントコンポーネントは、すでにおなじみの標準的な React コンポーネントです。これらは JavaScript としてブラウザに送信され、そこで実行およびハイドレートされて豊富なインタラクティブ性を提供します。Next.js App Router では、ファイルの先頭に "use client" ディレクティブを配置することで、コンポーネントをクライアントコンポーネントとして明示的にマークします。

クライアントコンポーネントを使用する場合

UI にインタラクティブ性やクライアントサイドの状態が必要な場合は、常にクライアントコンポーネントを使用する必要があります。

  • onClick、onChange などのユーザーイベントやフォーム送信の処理。
  • useState を使用したローカル状態の管理、または useReducer を使用した複雑なロジックの管理。
  • useEffect を介した副作用の利用(例:WebSocket の購読や DOM との対話)。
  • Geolocation や IntersectionObserver などのブラウザ API の使用。

最適なハイブリッドアーキテクチャ

現代の React の真の力は、両方のパラダイムを組み合わせることにあります。最善のプラクティスは、アプリケーションツリー全体をサーバーコンポーネントで構築し、インタラクティブ性が必要なツリーの特定の葉の部分にのみクライアントコンポーネントを「散りばめる」ことです。

例:ブログ投稿のレイアウト

ブログ投稿ページを構築することを想像してみてください。

  • レイアウト、ナビゲーション、およびブログコンテンツ自体は完全に静的であり、CMS またはデータベースからのデータフェッチが必要です。これらはすべてサーバーコンポーネントであるべきです。
  • ただし、投稿の下部には**「いいね」ボタンと「コメントセクション」**があります。これらには onClick ハンドラーとクライアントサイドの状態が必要です。これらの部分のみを別のファイルに抽出し、"use client" でマークし、サーバーコンポーネントのレイアウトにインポートします。
// This is a Server Component (default in Next.js)
import db from '@/lib/db';
import LikeButton from './LikeButton'; // Client Component

export default async function BlogPost({ id }) {
  // Direct, secure database access
  const post = await db.post.findUnique({ where: { id } });

  return (
    <article>
      <h1>{post.title}</h1>
      <div dangerouslySetInnerHTML={{ __html: post.content }} />
      
      {/* We pass static data to an interactive Client Component */}
      <LikeButton initialLikes={post.likes} postId={post.id} />
    </article>
  );
}

関連資料: SSR で苦労している場合は、Next.js Hydration Errors の修正に関するこのガイドをチェックしてください。

こちらもおすすめ

Advertisement

よくある質問

クライアントコンポーネント内でサーバーコンポーネントをインポートできますか?

できません。クライアントコンポーネントはブラウザで実行されるため、インポートするコンポーネントもすべてブラウザで実行され(そして JS バンドルに含まれ)ます。ただし、サーバーコンポーネントをクライアントコンポーネントの children プロップとして渡すことはできます。これにより、サーバーコンポーネントの環境が維持されます。

クライアントコンポーネントはサーバーでもレンダリングされますか?

はい!Next.js のようなフレームワークでは、クライアントコンポーネントは初期 HTML を生成するためにサーバーでプリレンダリングされます(SSR)。しかし、インタラクティブ性を持たせるためにクライアントにも送信され、ハイドレートされます。一方、サーバーコンポーネントはサーバーでのみレンダリングされ、クライアントでハイドレートされることはありません。

サーバーコンポーネントとクライアントコンポーネント間で状態を共有するにはどうすればよいですか?

サーバーコンポーネントはステートレスです。静的なデータ(データベースクエリの結果など)は、プロップを介してクライアントコンポーネントに渡すことができます。ショッピングカートやユーザーテーマのようなグローバルなインタラクティブな状態が必要な場合は、Zustand、Jotai、React Context などのライブラリを使用して、その状態をクライアントコンポーネントツリー内で完全に管理する必要があります。

詳細解説:コアメカニズム

表面下を見ると、根底にあるメカニズムはシステムの複雑な相互作用を明らかにします。現代の開発において、これらのメカニズムを理解することが、初心者と専門家を分けるものです。

この実用的な例を考えてみましょう。

// A comprehensive example demonstrating advanced patterns
class ServiceManager {
  constructor() {
    this.services = new Map();
    this.initialized = false;
  }

  register(name, service) {
    if (this.services.has(name)) {
      throw new Error(`Service ${name} already registered`);
    }
    this.services.set(name, service);
  }

  async initializeAll() {
    this.initialized = true;
    for (const [name, service] of this.services) {
      if (typeof service.init === 'function') {
        await service.init();
      }
    }
  }

  get(name) {
    if (!this.initialized) {
      console.warn('Accessing services before initialization');
    }
    return this.services.get(name);
  }
}

このパターンにより、ビジネス要件が変化しても、アーキテクチャはスケーラブルで堅牢なまま維持されます。これは、大規模なアプリケーションで大きな利益をもたらす基本的なアプローチです。

実世界での応用とスケーリング

これを本番環境で実装すると、新たな課題が生じます。並行性、状態管理、メモリリークを考慮する必要があります。

たとえば、高スループットシステムを扱う場合、あらゆるマイクロ最適化が重要になります。ローカル開発では明らかにならないボトルネックを特定するために、プロファイリングツールに頼ることがよくあります。

上記の図は、アプリケーションが水平方向にスケールする典型的なデプロイ戦略を示しています。

理解度チェック

よくある質問

SSR はインタラクティブなクライアントコンポーネントをサーバー上で初期 HTML にプリレンダリングしますが、React が状態とイベントリスナーをハイドレートできるように、JavaScript バンドル全体をブラウザに転送します。RSC はサーバー上でのみ実行され、特殊な UI フォーマット(RSC ペイロード)をストリーミングします。その依存関係とコードはクライアントブラウザにダウンロードされることはなく、クライアントバンドルサイズのオーバーヘッドはゼロになります。
はい。クライアントコンポーネントはサーバーコンポーネントを直接インポートできませんが、サーバーコンポーネントを子スロットとして渡すことはできます。<ClientWrapper><ServerPostContent /></ClientWrapper>。サーバーコンポーネントはバックエンドで実行され、レンダリングされた仮想ツリーはインタラクティブなクライアント境界にシームレスにスロットされます。
Server Actions は 'use server' でマークされた非同期関数で、サーバー上で安全に実行されます。インタラクティブなクライアントコンポーネントは、フォーム送信やイベントハンドラ内でそれらを直接呼び出してデータベースレコードを変更し、周囲のサーバーコンポーネントデータツリーの再検証を自動的にトリガーできます。
重い依存関係(日付ライブラリ、シンタックスハイライター、データ変換パッケージなど)を完全にサーバー上に保持することで、RSC はクライアントの JavaScript ペイロードを 30〜70% 削減します。クライアントスクリプトの実行が減ることで、ブラウザのメインスレッドが解放され、Largest Contentful Paint (LCP) が高速化され、Interaction to Next Paint (INP) のレイテンシが大幅に削減されます。
境界を越えて渡されるプロップはシリアライズ可能である必要があります。文字列、数値、ブール値、プレーンオブジェクト、配列、JSX 要素がサポートされています。関数、クラスインスタンス、シンボル、Date オブジェクトは、事前にシリアライズしないと直接渡すことはできません。
Share this article:

Stay Updated

Get the latest posts delivered straight to your inbox.

Free Developer Utilities

Free In-Browser Developer Tools

Clean AI CLI logs, build cron expressions, decode JWTs, and calculate chmod permissions offline.

Explore Tools
Advertisement
Next.js App Router動的再検証ガイド
nextjs

Next.js App Router動的再検証ガイド

Next.js App Routerのキャッシュアーキテクチャ、fetchリクエストのメモ化、revalidateTagによるデータキャッシュ無効化、オンデマンドISR再検証を習得しましょう。

Read more