React Server Componentsの基本と使い方を初心者向けに解説

React Server Componentsとは

皆さんは、Reactでアプリケーションを開発するときにクライアント側だけで状態管理や画面描画を行う方法を見聞きしたことがあるのではないでしょうか。 一方でサーバー側で一部の処理を肩代わりできると、ページの初期表示などを効率的に行える場面が増えるかもしれません。

React Server Components (以下、Server Components) は、React 18以降で提案されているアーキテクチャです。 サーバーとクライアントそれぞれの強みを組み合わせることで、アプリケーションのパフォーマンスや開発体験を快適にすることを目指しています。

従来のServer-Side Rendering(SSR)やStatic Site Generation(SSG)などと同じように、サーバー側で描画に必要なデータを取得してからHTMLを生成する仕組みは一見似ています。 しかしServer Componentsでは、コンポーネント単位でサーバー側に任せることができる点が特徴です。 クライアント側では行いたくない重い処理をサーバー側で片付けるイメージを持つとわかりやすいかもしれません。

このアーキテクチャを利用すると、ネットワーク経由で取得したデータをReactが直接受け取ることができます。 そのため、データ取得まわりのコードがシンプルになりやすいです。 さらに、サーバーだけで完結する処理はクライアント側へ転送されないので、JavaScriptのバンドルサイズを減らす効果も期待できます。

とはいえ初心者にとっては、「サーバー側にコンポーネントを配置するとは具体的にどういうことだろう?」と疑問に思うかもしれません。 いきなりすべてをServer Components化するわけではなく、部分的に取り入れることもできます。 特に実務では、読み込みが重いコンポーネントや非同期処理を頻繁に行う部分をサーバー側に任せる運用が考えられます。

後ほど詳しく解説しますが、Server Componentsを導入する場合、サーバー側でのみ実行できる処理(たとえばデータベース操作やAPIコールなど)と、クライアントでのみ実行できる処理(たとえばユーザー操作に応じたUI更新など)を明確に分ける必要があります。 これを正しく区別できるようになると、Reactアプリの構成がはっきりと整理されます。


Server Componentsが注目される理由

Reactを使って開発を始めると、クライアント上での状態管理と描画に慣れ親しむものです。 しかし、それだけでは解決しきれない課題も少なくありません。

たとえば、大量のデータを扱う画面で重いAPI処理やデータ変換が頻繁に起こるとします。 クライアント側で処理すると、その分だけ初期表示が遅くなり、ユーザーが待たされる時間が長くなるかもしれません。

そこでSSRを導入すると、サーバーがHTMLを生成してクライアントに送るため、初期表示は多少改善しやすいです。 ただし、全ページをサーバーでレンダリングするとなると、サーバーの負荷が大きくなり、キャッシュやスケーリングの検討が必要になります。

Server Componentsでは、特定のコンポーネントだけをサーバー側で実行させることができます。 この仕組みによって、クライアント側で動かす必要のないコードはサーバーに任せ、その結果をコンポーネントとして受け取ることができるのです。

これによるメリットの一つは、バンドルサイズの削減です。 通常のSSRでは、Reactのクライアント側バンドルとは別にサーバー側のレンダリングロジックも存在します。 Server Componentsを活用すると、サーバーで完結する部分のJavaScriptコードはクライアントに送られないため、ユーザーがダウンロードするファイルが小さくなる可能性があります。

また、サーバーとクライアントそれぞれの役割を最適に分割できるため、パフォーマンスの向上も期待できます。 データ取得ロジックなどはサーバー側でまとめて処理し、結果だけをクライアントに渡すほうが効率的なケースも少なくありません。

こういった特徴から、特に複雑なデータ処理を含むアプリケーションでServer Componentsが注目されています。 現状ではNext.jsなどのフレームワークがServer Componentsを活用できる仕組みを提供し始めていますが、今後Reactの標準機能として多くの場面で利用される可能性があります。


Server Componentsの仕組みとクライアントサイドとの役割分担

Server Componentsの概念を理解するには、サーバー側のコンポーネントとクライアント側のコンポーネントがどのように連携するのかを把握する必要があります。 React 18以降では、この連携を制御するための仕組みとして以下のような考え方が取り入れられています。

サーバー側での処理

  • データベースアクセスや外部APIの呼び出しなど
  • HTMLを生成してクライアントに返すためのロジック
  • 認証情報や機密データの取り扱い

サーバーでのみ実行する部分は、Node.jsなどのランタイム上で動作します。 Server Componentsはここで完結するため、機密性の高いデータを安全に扱いやすいです。 たとえば、APIキーや認証情報をクライアントに渡すことなく処理を済ませることができます。

クライアント側での処理

  • UIのインタラクション
  • 状態管理(ユーザーの操作に応じた表示の切り替え)
  • イベントハンドリング

クライアント側で実行したい処理は、従来のReactコンポーネントと同じように動作します。 ただし、Server Componentsと組み合わせる場合、クライアントコンポーネントがサーバーコンポーネントの返したHTMLを受け取る形になることがあります。

サーバーコンポーネントとクライアントコンポーネントの連携

Server Componentsを使うときには、サーバーコンポーネントで描画した結果をクライアント側に渡すという流れを意識するとよいでしょう。 この結果はシリアライズされた形でクライアントに送られ、クライアントコンポーネントのレンダリングに組み込まれます。

フレームワークでの分割

Next.jsの場合は、appディレクトリやpagesディレクトリの中でファイル名や設定を行い、サーバー側とクライアント側のコンポーネントを明示的に分けることが多いです。 クライアント側で動かしたいコンポーネントには"use client"を冒頭に書くなどの方法があります。 一方で、サーバー側でしか使わないライブラリをインポートするなど、細かな制御を行うことも可能です。


実務におけるServer Componentsの活用シーン

Server Componentsを活用すると、実務ではどのようなメリットが得られるでしょうか。 ここでは、代表的なシーンをいくつか挙げてみます。

複雑なデータ取得が必要なページ

ユーザーごとに異なるダッシュボードを表示するときや、大量のデータをまとめて表示する画面など、複雑なデータ取得が発生する場面があるかもしれません。 そのようなケースでは、サーバーコンポーネント内で直接データを取得し、結果だけをクライアントに返すとシンプルに作れます。

クライアント側では、すでに加工されたデータを受け取るため、ロジックが軽減されます。 結果として、クライアントでのレンダリングパフォーマンスが良好になりやすいです。

セキュリティ要件が厳しい部分

APIキーや秘密のトークンなど、外部に公開したくない情報をクライアントに渡さずに済むようになります。 サーバーコンポーネント内でのみAPIキーを扱い、クライアントには最終的なデータだけを返す仕組みにすることで、コードの漏洩リスクが抑えられます。

これまででもSSRやバックエンドAPIを活用すれば同様のことは可能でした。 しかし、Server Componentsではコンポーネントの流れの中で自然にサーバー側ロジックを埋め込めるため、コードの構成がすっきりしやすいです。

初期表示が鍵となるサイト

SEOを考慮する場合や、ユーザーが素早く必要な情報を見られるようにする場合、初期表示速度が大切になります。 Server Componentsなら、クライアントに大きなバンドルを送らずともサーバー側でコンテンツをある程度完成させて送信できるので、表示までの時間を短縮しやすいです。

SSRと似ていますが、Server Componentsはあくまでコンポーネント単位でのサーバー実行ができることが強みです。 全体をSSRにするとサーバー負荷が大きくなる懸念がありますが、部分的にサーバーコンポーネントを使うなら、比較的柔軟に調整できるかもしれません。


Server Components導入時の基本的な流れ

Server Componentsを導入する際には、フレームワーク側の設定やディレクトリ構成など、特有のルールが存在します。 ここでは、Next.js 13以降を例に挙げて、導入イメージを簡単に紹介します。

アプリケーションの構成

Next.js 13以降では、appディレクトリベースのルーティングが推奨されており、そこにServer ComponentsとClient Componentsを混在させる構成を取ります。 以下のような構成を考えてみてください。

my-app/
  ├─ app/
  │   ├─ layout.js
  │   ├─ page.js
  │   ├─ components/
  │   │   ├─ MyServerComponent.js
  │   │   ├─ MyClientComponent.js
  │   └─ ...
  ├─ package.json
  └─ ...

基本的にはappディレクトリ内の.js.jsxはServer Componentとして扱われます。 もしクライアント側で動かしたいコンポーネントがあれば、そのファイルの冒頭に"use client"と記述して明示的にクライアントコンポーネントであることを示します。

サーバーコンポーネントの例

たとえば、以下のようなサーバーコンポーネントがあるとします。

// app/components/MyServerComponent.js

// "use client" は書かないので、これはサーバーコンポーネントです

import React from "react";

// ここでデータベース呼び出しを行うなど、サーバー側のみで実行するロジックを書けます
// サンプルとして、外部APIからJSONを取得するイメージを示します

async function MyServerComponent() {
  const response = await fetch("https://jsonplaceholder.typicode.com/posts/1");
  const data = await response.json();

  return (
    <div>
      <h2>サーバー側で取得したデータ</h2>
      <p>タイトル: {data.title}</p>
      <p>本文: {data.body}</p>
    </div>
  );
}

export default MyServerComponent;

このコンポーネントはサーバーでのみ実行されます。 データ取得ロジックがサーバー上で動き、最終的にシリアライズされた結果がHTMLとしてクライアントに渡されます。

クライアントコンポーネントの例

同じプロジェクト内で、クライアント側にインタラクションが必要な場合は、コンポーネントの冒頭で"use client"を宣言します。

// app/components/MyClientComponent.js

"use client";

import React, { useState } from "react";

function MyClientComponent() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <h2>クライアント側のカウンター</h2>
      <p>カウント: {count}</p>
      <button onClick={() => setCount((prev) => prev + 1)}>増やす</button>
    </div>
  );
}

export default MyClientComponent;

こちらは、Reactが従来から行ってきたクライアントサイドのレンダリングを担当します。 ユーザーがボタンをクリックすると状態が変化し、その変化がUIに反映されます。

サーバーとクライアントを組み合わせる

ページコンポーネント(page.jsなど)では、サーバーコンポーネントとクライアントコンポーネントを組み合わせて使うことができます。

// app/page.js

import React from "react";
import MyServerComponent from "./components/MyServerComponent";
import MyClientComponent from "./components/MyClientComponent";

export default function Page() {
  return (
    <main>
      <h1>Server Componentsのサンプルページ</h1>
      <MyServerComponent />
      <MyClientComponent />
    </main>
  );
}

上記の例では、サーバーコンポーネントが先に実行され、生成された内容がクライアントに送られます。 続いてクライアントコンポーネントがクライアント上でレンダリングされ、ボタンをクリックしたときの動きなどを担当するイメージです。


Server Componentsを使う上での注意点

Server Componentsは便利に感じられるかもしれませんが、いくつか注意すべきポイントがあります。 これらを把握しておくことで、予期せぬ挙動やパフォーマンス問題を回避しやすくなります。

クライアント側ライブラリとの互換性

サーバーコンポーネント内では、クライアント側でしか動かないライブラリ(DOM操作を直接行うものなど)は利用できません。 エラーの原因になるので、もしそれらを使う場合はクライアントコンポーネントに切り分ける必要があります。

たとえば、window オブジェクトを直接参照する処理や、jQueryのようにブラウザAPIに依存するライブラリはサーバーコンポーネントでは動きません。 このようなライブラリはクライアントコンポーネントで呼び出すようにしましょう。

データ取得のタイミング

サーバーコンポーネントは、リクエストが発生するたびにサーバーで実行されます。 一方で、クライアントコンポーネントはマウント時やユーザーの操作時などに処理が行われます。 このタイミングの違いを意識して、どのデータをサーバー側で準備しておき、どのデータをクライアント側で取得するかを決めることが大切です。

状態管理の分割

Server Componentsでやり取りするデータは、基本的にサーバーで処理した結果を一方向にクライアントへ渡す形になります。 もしクライアント上で状態管理が必要な場合は、クライアントコンポーネント側でuseStateuseReducer、あるいはReduxなどを用いて状態管理を行うことが多いです。

サーバーコンポーネントとクライアントコンポーネントの間でリアルタイムに双方向のデータ共有をするのは難しいです。 そのため、リアクティブなUI部分はクライアントコンポーネントに集約する設計が一般的になるでしょう。

サーバー負荷のコントロール

サーバーコンポーネントを多用しすぎると、サーバーへのリクエスト回数や処理が増えてしまう恐れがあります。 特に高トラフィックなアプリケーションでは、必要な部分だけをサーバーコンポーネントにし、不要な部分はクライアント側で処理するなどの使い分けが重要です。

サーバー負荷を分散させるには、キャッシュの仕組みやSSR固有の最適化方法を検討することもあります。 ただし、Server Componentsはまだ新しい概念のため、公式ドキュメントの更新などを追いかけながら運用していく必要があります。


Server Componentsの具体的なコード例と構造

ここではServer Componentsとクライアントコンポーネントを組み合わせた、もう少し複雑な例を見てみましょう。 ユーザーのリストを表示し、ユーザーを選択すると詳細情報が表示されるイメージを考えます。

ディレクトリ構造の例

app/
  ├─ page.js
  ├─ components/
  │   ├─ UsersServer.js    (サーバーコンポーネント)
  │   ├─ UserDetailServer.js (サーバーコンポーネント)
  │   ├─ UserSelectionClient.js (クライアントコンポーネント)
  │   └─ ...
  └─ ...

サーバーコンポーネント例: ユーザー一覧の取得

// app/components/UsersServer.js
import React from "react";

async function UsersServer() {
  const response = await fetch("https://jsonplaceholder.typicode.com/users");
  const users = await response.json();

  return (
    <div>
      <ul>
        {users.map((user) => (
          <li key={user.id}>
            {user.name} ({user.email})
          </li>
        ))}
      </ul>
    </div>
  );
}

export default UsersServer;

このコンポーネントでは、ユーザー一覧をサーバー側で取得して表示しています。 ユーザー名とメールアドレスを一覧で表示するだけなら、これだけで十分かもしれません。

サーバーコンポーネント例: ユーザー詳細の取得

// app/components/UserDetailServer.js
import React from "react";

async function UserDetailServer({ userId }) {
  // userIdに応じてAPIを呼び出し、ユーザーの詳細を取得
  const response = await fetch(`https://jsonplaceholder.typicode.com/users/${userId}`);
  const user = await response.json();

  return (
    <div>
      <h3>{user.name}さんの詳細</h3>
      <p>ユーザー名: {user.username}</p>
      <p>メールアドレス: {user.email}</p>
      <p>所在地: {user.address.city}, {user.address.street}</p>
    </div>
  );
}

export default UserDetailServer;

ユーザーを選択したときにだけ、このサーバーコンポーネントを呼び出すような仕組みにします。 そうすれば、不要なAPI呼び出しは行われません。

クライアントコンポーネント例: ユーザー選択のUI

// app/components/UserSelectionClient.js

"use client";

import React, { useState } from "react";
import UserDetailServer from "./UserDetailServer";

function UserSelectionClient() {
  const [selectedUser, setSelectedUser] = useState(null);
  const [userId, setUserId] = useState("");

  const handleSubmit = (e) => {
    e.preventDefault();
    if (userId) {
      setSelectedUser(userId);
    }
  };

  return (
    <div>
      <form onSubmit={handleSubmit}>
        <label>
          ユーザーIDを入力:
          <input
            type="text"
            value={userId}
            onChange={(e) => setUserId(e.target.value)}
          />
        </label>
        <button type="submit">詳細を表示</button>
      </form>

      {selectedUser && <UserDetailServer userId={selectedUser} />}
    </div>
  );
}

export default UserSelectionClient;

ここでは、ユーザーIDを入力すると、そのIDに対応するユーザー詳細を表示するUIを用意しています。 ユーザー詳細の表示には、サーバーコンポーネントであるUserDetailServerを呼び出しています。

ページコンポーネントへの組み込み

最後に、ページコンポーネントpage.jsでこれらを組み合わせます。

// app/page.js
import React from "react";
import UsersServer from "./components/UsersServer";
import UserSelectionClient from "./components/UserSelectionClient";

export default function Page() {
  return (
    <div>
      <h1>ユーザー情報</h1>
      <h2>全ユーザー一覧</h2>
      <UsersServer />

      <h2>ユーザー詳細</h2>
      <UserSelectionClient />
    </div>
  );
}

このページを表示すると、サーバーコンポーネントUsersServerが実行されてユーザー一覧を取得し、クライアントにはHTMLとして渡されます。 同時にUserSelectionClientがクライアントコンポーネントとして動作し、ユーザーがIDを入力したときにUserDetailServerを呼び出します。


Server Components導入によるパフォーマンス最適化の考え方

Server Componentsを使うことで、クライアント側の処理負荷やバンドルサイズが減る場合があります。 しかし、実際には以下のような点を考慮しながら最適化を行う必要があります。

過剰なサーバー側呼び出しを避ける

部分的なServer Componentsであっても、アクセスが集中するとサーバー側での呼び出しが増えます。 単純に「サーバーコンポーネントを増やすほど良い」というわけではありません。 本当にサーバー側で処理する必要があるロジックだけをServer Componentsに切り出す工夫が大切です。

キャッシュ戦略

サーバー側で繰り返し実行される処理がある場合は、キャッシュを活用すると効果的です。 ReactやNext.jsの機能として提供されるキャッシュ機能や、Redisなどの外部キャッシュを検討することで、同じリクエストに対して無駄な計算を何度もしなくて済むようになります。

クライアントコンポーネントとのバランス

ユーザー操作が多くてインタラクティブな部分はクライアントコンポーネントに任せるほうがスムーズなこともあります。 そのため、どの範囲をサーバーコンポーネントとし、どこをクライアントコンポーネントにするのかは、開発するアプリケーションの特性に応じて検討すると良いでしょう。


学習時につまずきやすいポイントと対処法

初心者の皆さんがServer Componentsを学ぶ際、つまずきがちと感じられるポイントをいくつか挙げます。 これらを意識しておけば、トラブルシューティングのヒントになるでしょう。

クライアントコンポーネントでサーバー向けAPIを呼ぶとエラーが起きる

Server Componentsだと勘違いして、クライアントコンポーネント内でサーバー専用のライブラリ(例: ファイルシステム操作)を使おうとするとエラーになります。 使い分けが大事なので、明確に処理を分離する癖をつけましょう。

「use client」の書き忘れによる不具合

クライアントコンポーネントにするつもりが、宣言を忘れてサーバーコンポーネントとして扱われてしまうと、DOM操作に関するコードなどがエラーを起こす可能性があります。 ライブラリをインポートする段階からクライアント専用かどうかを確認するようにすると安全です。

デバッグ時の混乱

Server Componentsはサーバーサイドで実行されるコードとクライアントサイドで実行されるコードが混在するため、デバッグの手順がわかりにくいかもしれません。 React Developer Toolsやブラウザの開発者ツールだけでなく、Node.jsのログ出力なども併用して原因を探る方法を身につけると安心です。

Server Componentsが出力するエラーは、サーバーログとブラウザコンソールの両方を確認しないと追い切れないケースがあります。


なぜ初心者にとってServer Componentsが学びやすいのか

Server Componentsは、一見すると高機能で難しそうに見えます。 しかし、以下のような理由から初心者にとっても学びやすい面があります。

コンポーネント思考に集約できる

これまではSSRやバックエンドAPIなど、複数のレイヤーにまたがるように考える必要がありました。 Server Componentsでは、Reactのコンポーネントという視点でサーバーサイドの処理もまとめられるので、一貫した考え方を維持しやすいです。

部分導入が可能

アプリケーション全体を一気にServer Components化する必要はありません。 まずは一部の重い処理やデータ取得ロジックをServer Componentsに切り出して、効果を試すことができます。 学習中にいきなり大規模なリファクタリングをしなくても、少しずつ慣れていけるのはありがたいところです。

分かりやすいファイル構成

Next.jsなどを使えば、ディレクトリ構成がある程度ガイドされているので、自分がどこにファイルを置くべきか迷いにくいです。 サーバーコンポーネントとクライアントコンポーネントのファイルも分けやすく、ミスを減らしながら学ぶことができます。


今後の展望とReactエコシステムとの関わり

React Server Componentsはまだ新しい技術で、開発コミュニティからのフィードバックを受けながら変化を続けています。 安定版として広く普及するには時間がかかるかもしれませんが、Reactエコシステム全体でのサポートが進められています。

フレームワークとの統合

Next.jsをはじめ、いくつかのReactフレームワークがServer Componentsを活用するための機能を提供しています。 これらのフレームワークがどのようにサーバーとクライアントの処理を分けるか、その仕組みを追いかけると理解が深まります。

バンドルツールの対応

Server Componentsが普及すると、WebpackやViteなどのバンドルツールも最適化オプションを用意してくる可能性があります。 今はNext.jsの専用設定を使うのが一般的ですが、将来的にはより多様な選択肢が生まれるかもしれません。

エコシステム全体への影響

Reactがクライアントレンダリング中心のライブラリとして広まってきた歴史の中で、Server Componentsは新しいアプローチです。 このコンセプトが浸透すれば、他のフロントエンドライブラリやフレームワークにも似た仕組みが取り入れられる可能性があります。


まとめ

React Server Componentsは、サーバー側でコンポーネントを動作させられる仕組みです。 データ取得や複雑な処理をサーバーに任せられるため、クライアントの負荷やバンドルサイズを抑えるメリットがあります。

しかし、それは万能というわけではなく、サーバー負荷やキャッシュ戦略、クライアントコンポーネントとの切り分けなど、総合的に考える必要があります。 実務での利用シーンとしては、大規模データの表示や機密情報の保護、初期表示の高速化などが挙げられます。

初心者の皆さんがServer Componentsを始めるときには、まずは小さな部分から導入してみるのがおすすめです。 サーバーコンポーネントとクライアントコンポーネントを明確に分け、どちら側で何を行うかを意識すると、自然とReactアプリ全体の構造が整理されていくでしょう。

Server ComponentsはReactの新しい可能性を切り開く取り組みです。 サーバーとクライアントの役割を上手に分担しながら、最適なアプリケーションを目指してください。

Reactをマスターしよう

この記事で学んだReactの知識をさらに伸ばしませんか?
Udemyには、現場ですぐ使えるスキルを身につけられる実践的な講座が揃っています。