1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

改めてReact Server Componentsについて簡単にまとめてみた

Last updated at Posted at 2025-12-18

本記事はアジアクエスト Advent Calendar 2025の19日目の記事です。

はじめに

現在のReact、あるいはNext.jsを使う上で、第一の選択肢となるApp Router。
App Routerで実装するなら、React Server Components(以下、RSC)の理解は避けては通れません。
最近(2025年12月時点)ではRSCの脆弱性の発見により話題となり、その名を耳にした人は多いと思います。
そこで、改めてどういったものなのかをまとめてみました。

本記事では脆弱性については触れません。
あくまでRSCとは何かを整理する目的で執筆しております

0. React と Next.jsについて

本題に入る前に、現代のフロントエンド開発を支える2つの巨塔、React と Next.js について簡単におさらいしておきましょう。

React とは?

Meta(旧Facebook)が開発した、UI(ユーザーインターフェース)を構築するためのJavaScriptライブラリです。
最大の特徴は「コンポーネント指向」であり、ボタンやヘッダー、記事カードといった部品(コンポーネント)を作り、それらをブロックのように組み合わせて画面を構築します。

Next.js とは?

Vercelが開発している、Reactのためのフレームワークです。
Reactはあくまで「UIを作るためのライブラリ」であり、ルーティング(ページ遷移)やサーバー機能などは持っていません。
それら開発に必要な機能をオールインワンで提供するのがNext.jsです。

今回紹介するReact Server Components(RSC)は、名前の通りReact本体の機能です。
2020年に初めて概念として登場しました。当時はまだReactの実験的な機能でした。
2022年にリリースされたNext.js 13で実用化され、2023年のNext.js 13.4でRSCを基盤としたApp Routerを導入することで扱いやすくなりました。
React本体には、2024年にリリースされたReact 19で、安定版として導入されています。

本記事では簡単のため、主にNext.jsでのRSCについて記載していきます。

1. Pages RouterからApp Routerへ

RSCを理解するには、Pages RouterからApp Routerについても知る必要があります。

Pages Router (pages/) の世界

Next.js 13.4より前のNext.jsでは、Pages Routerという設計が用いられており、「ページ単位」でレンダリング戦略を決めていました。

  • このページは SSR (getServerSideProps)
  • このページは SSG (getStaticProps)

これは分かりやすい反面、「ページ内の9割は静的なのに、ヘッダーのユーザー名表示のためだけにページ全体をSSRにする」といった非効率が発生していました。また、データ取得ロジックがページ最上部に集中し、プロップスのバケツリレーが避けられませんでした。

App Router (app/) の世界

App Router は、「コンポーネント単位」 でサーバーかクライアントかを選択できるアーキテクチャです。

  • 重い処理やデータ取得はサーバーコンポーネントで
  • インタラクションが必要なボタンだけクライアントコンポーネントで

ページ全体をひとまとめにするのではなく、パズルのピースごとに最適な場所でレンダリングする。これがRSCの基本思想です。

2. Server Components と Client Components の役割分担

App Routerでは、すべてのコンポーネントはデフォルトでServer Componentです。

特徴 Server Components (デフォルト) Client Components
主な役割 データ取得、バックエンド連携、静的UI ユーザー操作、ブラウザAPI、State管理
実行場所 サーバーのみ クライアント(+ビルド/SSR時にサーバー)
バンドル クライアントへJSが送信されない JSとして送信される
宣言 なし ファイル先頭に 'use client'

ここでの最大のメリットは 「Zero Bundle Size」 です。
例えば、サーバーコンポーネント内で日付整形ライブラリを使っても、そのライブラリ自体はブラウザに送信されません。送られるのは計算済みの結果だけです。

3. 「混ぜて使う」ためのルールと関係性

RSCやAppRouterを使う上で最も躓きやすいのが、この2つのコンポーネントの 「関係性(境界線)」 です。

インポートの「一方通行」ルール

原則として、サーバーからクライアントは呼べますが、逆はできません。

  • Server → Client: OK
    サーバー側でデータを取得し、それを Client Component の Props として渡す

  • Client → Server: NG
    ブラウザで動くコードの中に、DB接続などのサーバーコードを含めることはできないため、直接 import するとエラーになります

「穴あけパターン」 (Composition)

「じゃあ、Client Component の内側(子要素)に Server Component を表示したい時はどうするの?」

例えば、Context Provider(Client)の中に、サーバーで取得した記事リスト(Server)を表示したい場合などです。ここで登場するのが Children Props を使った Composition(合成) です。

NGパターン(直接インポート):

'use client';
import ServerList from './ServerList'; // ❌ これはできない!

export default function ClientWrapper() {
  return (
    <div>
      <ServerList /> 
    </div>
  );
}

OKパターン(Childrenとして渡す):
親(Server Component)が責任を持って組み立て、Client Component は「穴(children)」を開けて待っている状態にします。

// ClientWrapper.tsx ('use client')
'use client';

export default function ClientWrapper({ children }: { children: React.ReactNode }) {
  // ここでState管理などができる
  return <div className="interactive-box">{children}</div>;
}

// page.tsx (Server Component)
import ClientWrapper from './ClientWrapper';
import ServerList from './ServerList';

export default function Page() {
  return (
    // 親であるServer Componentが、Clientの中にServerを「差し込む」
    <ClientWrapper>
      <ServerList />
    </ClientWrapper>
  );
}

コンポーネントツリーの骨格は Server Componentで作り、インタラクションが必要な『葉』や『中間層』だけをClient Componentにするといった考え方で理解しましょう。

4. App Router での実装例

ブログ記事の詳細ページを表示する例です。

app/blog/[slug]/page.tsx (Server Component)

import { db } from '@/lib/db';
import LikeButton from './LikeButton'; // Client Component

// async/await がコンポーネントで使える!
export default async function BlogPost({ params }: { params: { slug: string } }) {
  // 1. サーバー上で直接データ取得
  const post = await db.post.findUnique({ where: { slug: params.slug } });

  if (!post) return <div>Not Found</div>;

  return (
    <article>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
      
      {/* 2. インタラクティブな部分はClient Componentに委譲 */}
      <div className="mt-4">
        {/* シリアライズ可能なデータ(IDなど)をPropsで渡す */}
        <LikeButton postId={post.id} initialLikes={post.likes} />
      </div>
    </article>
  );
}

app/blog/[slug]/LikeButton.tsx (Client Component)

'use client'; // Client Componentであると宣言

import { useState } from 'react';
import { updateLikes } from '@/app/actions'; // Server Actions

export default function LikeButton({ postId, initialLikes }: { postId: string, initialLikes: number }) {
  const [likes, setLikes] = useState(initialLikes);

  return (
    <button 
      onClick={async () => {
        const newCount = await updateLikes(postId); // サーバー処理を呼び出し
        setLikes(newCount);
      }}
    >
      👍 {likes}
    </button>
  );
}

このように、データ取得ロジックとUI表示(Server)、そしてインタラクション(Client)が、ファイルシステム上で自然に共存できるのが App Routerの強みです。

まとめ

RSCとApp Routerは、Reactアプリケーションの設計思想そのものを変えるアップデートでした。

  • 重い処理はサーバーへ
  • インタラクションはクライアントへ

というように役割分担が明確化されました。

  • 基本はRSCで書く
  • 必要な時だけクライアントへ降りる ('use client')
  • ネストが必要なときは children で合成する

この3点を意識して使いこなすことが、RSCでの開発の鍵となるでしょう。
少しでも理解の一助になれば幸いです。

参考

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?