React

React Server Components(RSC)とは?サーバー・クライアントの違いと使い分けを実装例で解説

React Server Components(RSC)とは?サーバー・クライアントの違いと使い分けを実装例で解説

React Server Components(RSC)は、コンポーネントをサーバー上でのみ実行し、その結果だけをクライアントへ送るReactの仕組みです。React 19で安定版となり、Next.jsのApp Routerでは全コンポーネントが既定でサーバーコンポーネントになります。混乱しやすいのは「サーバーコンポーネントを表す専用ディレクティブは存在しない」点で、目印を付けるのは逆に'use client'を書くクライアント側です。本記事では使い分け、SSRとの違い、RSCペイロードの中身、Next.js 16のCache Components、使うべきでない場面までを実コード付きで整理します。

まとめ:RSC導入前に押さえる5点と判断の分かれ目

  • 既定はサーバー:useState・onClick・ブラウザAPIを使う部分だけ'use client'でクライアント化する。
  • 専用ディレクティブは無い:'use server'はサーバーコンポーネントの印ではなく、サーバー関数(Server Actions)に付けるもの。
  • SSRとは別の概念:SSRは描画方式、RSCは実行場所の分担。排他ではなく両立して使う。
  • 境界は実物で確認する:サーバーからクライアントへはRSCペイロードが流れる。中身を見れば境界の良し悪しを観察で判定できる。
  • 旧命名は廃止:.server.jsx/.client.jsxでの区別は2020年の実験的仕様。現在はディレクティブで区別する。

分かれ目はひとつ。画面の大半が操作UIならRSCの効き目は薄く、データ取得が主で操作が局所的ならほぼ確実に効きます。

React Server Components(RSC)とは何かと実行される環境

従来のReactコンポーネントがブラウザで実行されバンドルに含まれていたのに対し、サーバーコンポーネントのコードはクライアントのJavaScriptバンドルに含まれません。Markdownパーサーや日付整形ライブラリのような重い依存を使っても、ブラウザに送るコード量は増えません。

実行タイミングは2つ。ビルド時にCI/CDサーバー上で走らせる使い方と、リクエスト時にWebサーバー上で走らせる使い方です。静的な内容は前者、リクエストごとに変わるデータは後者が向きます。React公式のServer Componentsリファレンスも、この2つを区別しています。

サーバーコンポーネントはデータベースやファイルシステムへ直接アクセスでき、async/awaitでデータ取得をコンポーネント内に書けます。一方でuseState・useEffectなどのフックやイベントハンドラは持てず、Reactのcontextも受け取れません。操作に反応する部分は、クライアントコンポーネントが担当します。

React 19でRSCの仕様は安定版となり、マイナーバージョン間で壊れないことが保証されました。npmのlatestは2026-09-11時点で19.3.0、2026-09-09公開のリリースノートにはViewTransitionやFragmentへのref対応が並びます。ただし公式が注意するとおり、RSCを「実装する側」のAPIは完全には固まっていません。フレームワークやバンドラーには特定のReactバージョンへのピン留めが推奨されており、実務ではpackage.jsonで版を固定して運用します。React 19そのものの変更点はReact 19の新機能とReact Compilerの解説で整理しました。

サーバーコンポーネントとクライアントコンポーネントの違いと使い分け

検索でも「サーバー vs クライアント」の使い分けが最も多く問われます。判断の起点はその部分にインタラクティブ性(状態・イベント・ブラウザAPI)が要るかだけ。要らなければサーバー、要ればクライアントへ置きます。

観点 サーバーコンポーネント(既定) クライアントコンポーネント
実行場所 サーバーのみ サーバーで初期HTML生成後も実行
useState / useEffect 使えない 使える
onClick等のイベント 付けられない 付けられる
React context 受け取れない 受け取れる
DB・ファイルへの直接アクセス できる できない(API経由)
JSバンドルへの含有 含まれない 含まれる
向く用途 データ取得・静的表示・重い依存 フォーム・モーダル・タブ

‘use client’境界を葉側に寄せてバンドルを軽くする指針

クライアント化はファイル単位で、先頭に'use client'を書きます。押さえるべきはツリー全体ではなく「操作が必要な葉」だけをクライアントにすること。React公式のuse-clientリファレンスにあるとおり、このディレクティブはモジュールグラフを分ける境界の宣言で、境界を根に近づけるほどバンドルは膨らみます。

// LikeButton.tsx — 操作するのでクライアント
'use client';
import { useState } from 'react';

export function LikeButton() {
  const [liked, setLiked] = useState(false);
  return <button onClick={() => setLiked(!liked)}>{liked ? '♥' : '♡'}</button>;
}
// Article.tsx — データ取得は上位のサーバーコンポーネントで
import { LikeButton } from './LikeButton';

export default async function Article({ id }) {
  const post = await db.post.find(id); // サーバーで直接取得
  return (
    <article>
      <h1>{post.title}</h1>
      <p>{post.body}</p>
      <LikeButton />
    </article>
  );
}

よくある誤解が「境界の内側はすべてクライアント送り」という理解です。取り込まれるのは、そのファイルがimport しているモジュールと直接描画している子だけ。childrenやpropsとして渡されたサーバーコンポーネントは、描画済みの結果として差し込まれます。モーダルの外枠だけをクライアントにして中身をサーバーに残す構成が成立するのは、この性質によるものです。

Client Componentへ渡せるpropsとシリアライズの制約

サーバーからクライアントへ渡せるのは直列化できる値だけです。文字列・数値・真偽値・null・undefinedのほか、配列・Map・Set・Date・FormData・プレーンオブジェクト、Promiseも渡せます。渡せないのは関数(サーバー関数を除く)、クラスのインスタンス、JSX要素、グローバル登録されていないSymbol。ORMが返すモデルをそのまま渡して落ちるのが定番の詰まりどころで、プレーンオブジェクトへ詰め替えてから渡します。

Promiseを渡せる性質は使いどころが広い。awaitせずPromiseのまま渡してクライアント側でuseAPIで読むと、データ待ちの間に周辺UIを先に描けます。書き方はReactのuseフック(use API)の使い方にまとめました。

サーバーとクライアントのどちらで動いているかを確かめる3つの方法

判別は3つで足ります。第一に、ファイル先頭に'use client'があればクライアント、無く親からも境界に入っていなければサーバー。第二に、console.logの出力は、サーバーコンポーネントならブラウザではなくターミナルに出ます。第三に、React Developer Toolsではサーバーコンポーネントが専用のタグ表示になり、区別が付きます。

クライアントコンポーネントを切り出してカタログ化しておくと、境界の内側に何が入っているかがレビューで見えます。作り方はStorybookの使い方とNext.js連携で解説しました。

RSCとSSR(サーバーサイドレンダリング)の違いと併用の関係

「reactサーバーサイド」で検索して両者を同じものと捉えると設計を誤ります。SSRは描画のタイミングの話、RSCはコンポーネントの実行場所の話で、レイヤーが異なります。

SSR RSC
何を決めるか HTMLをいつ・どこで生成するか どのコンポーネントをサーバーで実行するか
クライアントへ送るJS 全コンポーネント分を送る サーバーコンポーネント分は送らない
ハイドレーション ツリー全体が対象 クライアント境界の内側だけが対象
関係 排他ではない。RSCの初期HTMLはSSRで生成され併用される

従来のSSRでは、サーバーで一度HTMLを作った後、同じコンポーネントのJavaScriptをすべてクライアントへ送り、ブラウザ側で再度実行(ハイドレーション)していました。RSCは操作の要らない部分のJSを最初から送らないため、ハイドレーション対象とバンドルサイズが同時に減ります。出力がずれたときのエラーの直し方はハイドレーションエラーの原因と対処法に、描画方式そのものの違いはSSR・CSR・SSG・ISG・ISRの違いと使い分けに、Next.jsでの選び方はNext.jsの主要なレンダリング方式にまとめました。認証の観点での差はCSRとSSRの認証の違いが参考になります。

RSCペイロードの中身と初回表示からハイドレーションまでの流れ

サーバーとクライアントの間を実際に流れているものを見ておきます。

RSCペイロードが運ぶ3種類の要素と画面が操作可能になるまでの順序

サーバーコンポーネントは、HTMLではなくRSCペイロードという専用の直列化形式へ描画されます。Next.js公式のServer and Client Componentsによると、運ばれるのは次の3種類です。

  • サーバーコンポーネントを描画した結果そのもの
  • クライアントコンポーネントを差し込む位置のプレースホルダと、そのJavaScriptファイルへの参照
  • サーバーコンポーネントからクライアントコンポーネントへ渡したprops

初回ロードでは、まずHTMLが届いて操作できない画面が即座に出ます。続いてRSCペイロードでサーバー側とクライアント側のツリーを突き合わせ、最後にJavaScriptがクライアントコンポーネントをハイドレートして操作可能になる。2回目以降の遷移ではHTMLを作り直さず、RSCペイロードだけを先読みしてキャッシュします。

使い道は明快です。開発者ツールでナビゲーション時のレスポンスを覗くと、propsとして渡した値がそのまま乗っているのが見えます。ここに個人情報や内部IDが乗っていれば、境界の引き方を見直す合図。渡す値を絞れば、そのままペイロードが軽くなります。

Suspenseでストリーミングする単位の切り方と待ち時間の見せ方

データ取得が遅い部分はSuspenseで包み、フォールバックを先に返します。包んだ境界の単位でストリーミングされるため、遅いAPIを1本抱えた区画のせいで画面全体が止まる事態を避けられる。単位は「同時に出したいUIのかたまり」で切るのが基準で、細かく切りすぎると読み込み中の表示が散らかります。

注意点をひとつ。Suspenseで包むこと自体は、動的描画へ切り替えるスイッチではありません。同期処理しか行わないコンポーネントは、包んであってもビルド時の事前描画で完了します。待ち時間の見せ方を決める道具であって、描画タイミングを決める道具ではない。

Next.js App Routerでのデータ取得とServer Actionsの実装

RSCを本番で使う現実的な選択肢はNext.jsのApp Router(app/ディレクトリ)です。npmのlatestは2026-09-11時点で16.3.4。ファイルを作った時点でサーバーコンポーネントになります。規約の詳細はApp Routerのファイル規約と動的ルーティングに、機能一覧はNext.js 16の概要にまとめました。

サーバーコンポーネント内での直接のデータ取得と再検証間隔の指定

データ取得はコンポーネントをasyncにして直接awaitします。useEffectとローディング状態の管理は不要になり、クライアントへ生データを渡す必要もありません。APIキーをヘッダに載せる処理も、サーバー側に閉じたまま書けます。

// app/users/page.tsx(サーバーコンポーネント)
export default async function UsersPage() {
  const res = await fetch('https://api.example.com/users', {
    next: { revalidate: 60 }, // 60秒キャッシュ
  });
  const users = await res.json();
  return (
    <ul>
      {users.map((u) => (
        <li key={u.id}>{u.name}</li>
      ))}
    </ul>
  );
}

Server Actionsでの更新処理と引数を信用しない検証の置き場所

フォーム送信などの更新処理は、'use server'を付けた関数(Server Action)で書きます。クライアントから呼び出せるサーバー側の関数で、APIエンドポイントを別途用意せずに済む。'use server'はサーバーコンポーネントの目印ではない、という点だけ混同しないようにしてください。

// app/actions.ts
'use server';
export async function createTodo(formData: FormData) {
  const session = await auth();            // 1. 認可を先に確認
  if (!session) throw new Error('forbidden');
  const title = String(formData.get('title') ?? '').trim();
  if (title.length === 0 || title.length > 120) {
    throw new Error('invalid title');      // 2. 引数を検証
  }
  await db.todo.create({ title, userId: session.userId });
}
// フォーム側(サーバーコンポーネントのまま送信できる)
import { createTodo } from './actions';

export default function TodoForm() {
  return (
    <form action={createTodo}>
      <input name="title" />
      <button>追加</button>
    </form>
  );
}

認可と検証を先頭に置くのには理由があります。React公式のuse-serverリファレンスは、サーバー関数の引数は完全にクライアント制御下にあるため信頼できない入力として検証・エスケープし、ログイン中のユーザーにその操作の権限があるかを関数内で必ず確かめるよう明記しています。Server Actionは事実上の公開エンドポイントで、URLが見えないだけ。フォーム側の書き方はReact 19のform actionの使い方で扱っています。

Next.js 16のCache Componentsとuse cacheの位置づけ

RSCの実装で最後に効くのがキャッシュ設計です。Next.js 16系では、この部分がCache Componentsという枠組みに整理されました。押さえるべきは「キャッシュされた結果はRSCペイロードとして保存される」一点で、キャッシュ設計はそのままペイロード設計になります。

Cache Componentsを有効にする設定と旧キャッシュ方式の扱い

有効化は設定ファイルのフラグ1つ。Next.js公式のキャッシュドキュメントによると、Cache Componentsを入れると部分事前描画(PPR)が既定になり、静的な外枠を先に返しつつ動的な部分だけをリクエスト時に流す構成になります。フラグを立てなければ従来のキャッシュモデルが継続するため、既存プロジェクトが即座に壊れることはありません。

// next.config.ts
import type { NextConfig } from 'next';

const nextConfig: NextConfig = {
  cacheComponents: true,
};

export default nextConfig;

use cacheでキャッシュする単位とcacheLifeの指定

'use cache'は非同期関数やコンポーネントの戻り値をキャッシュするディレクティブで、データ単位(取得関数)とUI単位(コンポーネントやページ)の2階層で使えます。引数と親スコープから捕捉した値が自動的にキャッシュキーへ入るため、ユーザーごとに違う結果を1つのキーへ混ぜる事故は起きにくい。公式のuse cacheリファレンスは、寿命を決めるcacheLifeの併記を勧めています。省略すると既定プロファイルが当たります。

// app/lib/data.ts
import { cacheLife } from 'next/cache';

export async function getUsers() {
  'use cache';
  cacheLife('hours');
  return db.query('SELECT * FROM users');
}

逆に、リクエストごとに新しい値が要る処理へ'use cache'を付けてはいけません。cookies()やheaders()を読む区画はSuspenseで包み、事前描画の外へ出します。保存先が「事前描画HTML」「サーバー側の共有ストア」「ブラウザ」に分かれる点を含め、全体像はNext.jsのキャッシュ|5つの保存先とuse cacheの使い分けで扱っています。

サーバーとクライアントの境界設計でつまずく典型パターンと対処

RSCの事故は文法エラーではなく、設計の置き場所ミスとして現れます。

境界を根に置いてクライアントバンドルが膨らむ失敗と切り分けの目安

いちばん多いのが、ルートレイアウトや共通コンポーネントに'use client'を付けてしまう型です。状態管理ライブラリのプロバイダをアプリ全体で包むと、配下のツリーがまるごとクライアント側へ移り、RSCの効き目がほぼ消える。プロバイダはchildrenを受け取るクライアントコンポーネントとして切り出し、ツリーの深い位置へ置くのが定石です。

切り分けの目安は数値で持てます。ビルド出力のFirst Load JSがRSC導入前後でほとんど変わっていないなら、境界が根に寄りすぎている合図。テーマ切り替えのプロバイダ1つのために全ページのJSが増えていないか、ビルドのたびに確かめます。

server-onlyパッケージで秘匿値のクライアント流出を止める

モジュールは双方から取り込めるため、APIキーを含む関数をうっかりクライアントから読み込む事故が起こり得ます。Next.jsではNEXT_PUBLIC_接頭辞の付かない環境変数がクライアントバンドルで空文字に置換されるため値そのものは漏れませんが、壊れ方が分かりにくい。server-onlyを読み込んでおくと、クライアントから取り込んだ時点でビルドエラーになります。

// lib/data.ts
import 'server-only';

export async function getData() {
  const res = await fetch('https://external-service.com/data', {
    headers: { authorization: process.env.API_KEY },
  });
  return res.json();
}

境界の取り違えは、バンドルだけの問題では済みません。2025年にはRSCの実装そのものに深刻な脆弱性(CVE-2025-55182)が報告され、対象バージョンの確認と更新が必要になりました。影響の判定と更新の手順はReact2Shell(CVE-2025-55182)の対応手順にまとめています。対応手順はReact Server Componentsの脆弱性(CVE-2025-55182)の解説にまとめています。版を追い続ける運用体制まで含めて採用の是非を判断してください。

データのキャッシュが効きすぎて更新が反映されない事象の切り分け

「管理画面で更新したのに一覧が古いまま」という相談の大半は、キャッシュ境界の設計漏れです。切り分けは3段階。まずビルド時の事前描画で固まっていないか、次にサーバー側のデータキャッシュが残っていないか、最後にブラウザが保持するRSCペイロードが古くないか。cacheLifeの寿命と再検証の対象タグが食い違っているケースが目立ちます。暫定対処でキャッシュを全面的に切ると、RSCで得たはずの速度が消える。更新系の処理から対象タグを再検証する経路を先に通してください。

RSCを採用しない判断とApp Router移行の見積もり観点

RSCは万能ではありません。向かない構成ははっきりしており、全面採用しない判断のほうが妥当な場面もあります。

管理画面のようにRSCを全面採用しない方が単純になるアプリの条件

  • 操作が中心のUIばかりのアプリ:ダッシュボードのようにほぼ全要素がインタラクティブなら、大半が'use client'になり恩恵が薄い。従来のCSR/SPA構成のままのほうが単純。
  • App Routerへ移行できない既存資産:Pages RouterやRSC非対応の構成では使えない。移行コストに見合うかを先に評価する。
  • 境界設計に慣れていないチーム:propsの直列化制約やキャッシュ挙動を含め学習コストがある。レビューできる人が1人もいない状態での新規採用は避ける。
  • 細かなキャッシュ制御が要件:意図しないキャッシュで「更新が反映されない」問題が起きやすく、キャッシュ境界を明示的に設計する工数が要る。

逆に、記事・ECの一覧やLPのようにデータ取得が主で操作が限定的なページは、バンドル削減と初期表示の両面で効きます。全面採用か不採用かの二択ではなく、ページ単位で決めて構いません。

Pages RouterからApp Routerへ移行する際の見積もり項目

移行の工数は「ページ数」ではなく「クライアント境界の引き直し量」で決まります。数えるべきは、状態管理プロバイダの配置、getServerSideProps/getStaticPropsからの書き換え本数、'use client'を付けざるを得ないサードパーティ製UIライブラリの数、キャッシュ再検証の経路。App RouterとPages Routerは同一プロジェクト内で共存できるため、トラフィックの少ない画面から段階的に移す進め方が現実的でしょう。入口はNext.jsとは?16系App Routerで最小アプリを動かす手順です。

外部の開発会社にRSC前提のNext.js構成を任せるときの確認項目

発注時に確認すべきは実績年数ではなく成果物の具体です。クライアント境界の一覧が設計書に落ちているか、キャッシュ寿命と再検証タグの対応表があるか、Reactとフレームワークの版を誰がどう上げるかが決まっているか。この3点が空欄のまま進む案件は、公開後にキャッシュ事故で止まります。社内に境界設計をレビューできる人がいないまま内製へ踏み切るより、業務用・Webアプリ開発のように要件定義から運用まで通して引き受けられる体制へ相談し、設計を成果物として残してもらうほうが移行後の保守は軽くなります。

よくある質問

RSCでよく出る質問に短く答えます。

RSCとは何ですか?

コンポーネントをサーバー上でのみ実行し、その結果だけをクライアントへ送るReactの仕組みです。そのJavaScriptはブラウザに送られません。React 19で正式版となり、npmのlatestは2026-09-11時点で19.3.0です。

SSRとの違いは何ですか?

SSRは「HTMLをサーバーで生成する描画方式」、RSCは「どのコンポーネントをサーバーで実行しJSを送らないか」の分担です。RSCの初期HTMLはSSRで生成されるため両者は併用されます。ハイドレーションの対象範囲が狭まる点が実際の差です。

‘use client’はどこに書きますか?

クライアント側の動作(useStateやイベントハンドラ、ブラウザAPI)が必要なファイルの先頭に書きます。importするモジュールと直接描画する子もクライアント側に含まれるため、操作が必要な末端に近い場所へ置くのが基本。childrenとして渡したサーバーコンポーネントは含まれません。

サーバーコンポーネントでuseStateは使えますか?

使えません。useEffectなどのフックやReactのcontextも同様です。状態やイベントを扱う部分は'use client'を付けたクライアントコンポーネントへ切り出します。

.server.jsxや.client.jsxという拡張子は今も使いますか?

使いません。拡張子で区別する方式は2020年の実験的仕様で、現在は廃止されています。区別はファイル先頭の'use client'ディレクティブで行う。古いサンプルにはこの表記が残っているため、参照する資料の年代を確かめてください。

関連記事

お気に入りに入れた記事の一覧

この記事は以下の記事からリンクされています

ほか 13 件の記事からもリンクされています。

資料請求

今日のトレンド記事 直近 24 時間で、いつもより多く読まれている記事

  1. 2026.10.23 テックブログ Renta!不正アクセスでサービス停止:再開未定の現状と運営側の停止判断・証拠保全・段階的再開
  2. 2025.01.09 コラム アドレサブルCM(Addressable CM)とは?フジテレビで出る表示の意味・仕組みと視聴データの扱い
  3. 2026.10.21 テックブログ ふるさとチョイス不正アクセスで445万件流出:対象確認・パスワード変更と再開時期
  4. 2026.07.09 テックブログ Muse Imageとは?Metaの画像生成AIの使い方・API料金(1枚0.01ドル)と日本での提供状況【2026年10月】
  5. 2025.12.10 コラム 今の50代は何世代?バブル世代・団塊ジュニア・氷河期の境目と特徴

RELATED POSTS 関連記事

目次