React Server ComponentsとClient Components:徹底解説

Table of Contents
React Server Components (RSC) の登場は、React アプリケーションのアーキテクチャパラダイムを根本的に変えました。RSC 以前は、React は主にブラウザでのインタラクティブなコンポーネントのレンダリング(または、時折 SSR を介して HTML にプリレンダリングすること)に焦点を当てていました。React Server Components の登場により、サーバー上でのみ実行されるコンポーネントと、クライアント上で実行されるコンポーネントという明確な分割が生まれました。
このガイドでは、このハイブリッドアプローチの核となる違い、アーキテクチャ上の利点、そしてサーバーコンポーネントとクライアントコンポーネントのどちらを使用するかを決定するための意思決定マトリックスについて詳しく説明します。
React Server Components とは?
React Server Components は、サーバー上でのみ実行されるコンポーネントです。これらは JavaScript としてブラウザに送信されることはありません。代わりに、サーバーが React コンポーネントのロジックを実行し、データベースや内部 API から必要なデータを直接フェッチし、結果の HTML/UI をクライアントに直接ストリーミングします。
サーバーコンポーネントの利点
- クライアントサイド JavaScript がゼロ: サーバーコンポーネントはブラウザで実行されないため、アプリケーションの JavaScript バンドルに重さを加えることはありません。重いマークダウンパーサーや日付フォーマットライブラリのような大規模なライブラリをサーバーコンポーネント内でインポートしても、ユーザーのネットワーク接続に負担をかけることはありません。
- 直接データアクセス: サーバーコンポーネントは信頼できる環境で実行されます。中間 API ルートを立ち上げたり、
useEffectのデータフェッチのウォーターフォールに対処したりすることなく、コンポーネントから直接データベースを安全にクエリできます。 - 初期ロード時間の改善: サーバーはレンダリングされた UI をクライアントに段階的にストリーミングできるため、ユーザーは大規模な JS バンドルのダウンロードとハイドレーションを待つよりもはるかに速くページを表示し、操作できます。
注意点:インタラクティブ性がない
サーバーコンポーネントはブラウザで実行されないため、ブラウザ専用の API やクライアントサイドの状態に依存する React フックを使用できません。サーバーコンポーネント内では、useState、useEffect、onClick、または window.localStorage を使用できません。
クライアントコンポーネントとは?
クライアントコンポーネントは、すでにおなじみの標準的な 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 の修正に関するこのガイドをチェックしてください。
- Zustand vs Jotai State Management Comparison
- Next.js App Router Dynamic Revalidation Guide
- Custom React Hook Performance Optimization Patterns
こちらもおすすめ
- Understanding React Hydration Mismatch and Server Components
- Next.js Server Actions
- Handling Forms in Next.js 14: Server Actions vs Client Components
- React 19 Server Actions & Optimistic Updates (Zero Lag)
よくある質問
クライアントコンポーネント内でサーバーコンポーネントをインポートできますか?
できません。クライアントコンポーネントはブラウザで実行されるため、インポートするコンポーネントもすべてブラウザで実行され(そして 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);
}
}
このパターンにより、ビジネス要件が変化しても、アーキテクチャはスケーラブルで堅牢なまま維持されます。これは、大規模なアプリケーションで大きな利益をもたらす基本的なアプローチです。
実世界での応用とスケーリング
これを本番環境で実装すると、新たな課題が生じます。並行性、状態管理、メモリリークを考慮する必要があります。
たとえば、高スループットシステムを扱う場合、あらゆるマイクロ最適化が重要になります。ローカル開発では明らかにならないボトルネックを特定するために、プロファイリングツールに頼ることがよくあります。
上記の図は、アプリケーションが水平方向にスケールする典型的なデプロイ戦略を示しています。
理解度チェック
よくある質問
Free In-Browser Developer Tools
Clean AI CLI logs, build cron expressions, decode JWTs, and calculate chmod permissions offline.
Related Articles

Next.js App Router動的再検証ガイド
Next.js App Routerのキャッシュアーキテクチャ、fetchリクエストのメモ化、revalidateTagによるデータキャッシュ無効化、オンデマンドISR再検証を習得しましょう。
Read more
Next.js14 App Routerのキャッシュを解明する:古いデータのデバッグ
Next.jsApp RouterのキャッシングレイヤーであるRequest Memoization、Data Cache、Full Route Cache、Router Cacheを習得し、予期せぬ古いデータバグを防ぎましょう。
Read more
Next.jsにおけるsuppressHydrationWarning: 安全な利用法とデバッグの完全ガイド
Next.jsのsuppressHydrationWarningについて、安全な利用法とデバッグ方法を実証済みの本番環境での例を交えて網羅的に解説する包括的なガイドです。
Read more