React Server Components完全ガイド:フロントエンド開発の新時代を理解する

20フロントエンド

この記事では、RSCの基本概念から実際の導入方法、そして僕が実際に使ってみて感じたメリット・デメリットまで、リアルな体験談を交えながら解説していきます。

最近、フロントエンド開発界隈でめちゃくちゃ話題になっているReact Server Components(RSC)について、今日は徹底的に掘り下げてみたいと思います。僕自身、この技術に出会った時は正直「また新しい概念かよ...」って思ったんですが、実際に触ってみると、これはマジでゲームチェンジャーだと感じました。

この記事では、RSCの基本概念から実際の導入方法、そして僕が実際に使ってみて感じたメリット・デメリットまで、リアルな体験談を交えながら解説していきます。フロントエンド開発に携わる同世代のエンジニアの皆さんに、少しでも参考になれば嬉しいです。

React Server Componentsとは何なのか?

従来のReactの課題

まず、なぜReact Server Componentsが生まれたのかを理解するために、従来のReactアプリケーションが抱えていた課題について話しましょう。

僕が大学時代にReactを学び始めた頃(もう3年前になりますが)、SPAの世界は本当に魅力的でした。ページ遷移が爆速で、UXが素晴らしく、「これが未来のWebアプリだ!」って興奮したものです。でも、実際に本格的なアプリケーションを作るようになると、いくつかの問題に直面しました。

JavaScript バンドルサイズの肥大化

最初に作ったポートフォリオサイトは、シンプルなのにバンドルサイズが500KB超えていました。なぜかというと、必要なライブラリを片っ端から入れていたからです。react-router、axios、moment.js、lodash...。気づいたら初期ロードに3秒以上かかっていて、モバイルで見た時の遅さに愕然としました。

SEOの問題

クライアントサイドレンダリング(CSR)の最大の欠点は、検索エンジンによるクロールの問題です。僕が作った技術ブログも最初はCSRで作っていたんですが、Google Search Consoleを見ると、記事がほとんどインデックスされていませんでした。これは結構ショックでしたね。

初期表示の遅さ

CSRでは、まずJavaScriptをダウンロードして実行してからコンテンツが表示されます。特にモバイル環境や回線の遅い環境では、「白い画面」を見ている時間が長すぎて、ユーザーが離脱してしまうことが多々ありました。

React Server Componentsの登場

これらの課題を解決するために登場したのが、React Server Componentsです。2020年にReactチームから初めて発表された時、正直「また複雑な概念が増えるのか...」と思いました。でも、実際に理解してみると、これは本当に革命的なアプローチだと気づきました。

RSCの核心的なアイデアは**「サーバーでReactコンポーネントをレンダリングして、その結果をクライアントに送信する」**ことです。ただし、従来のSSR(Server-Side Rendering)とは大きく異なります。

従来のSSRとの違い

SSRの仕組みと限界

従来のSSRについて、僕の理解を整理してみます。SSRでは:

  1. サーバーでReactコンポーネントをHTMLにレンダリング
  2. そのHTMLをクライアントに送信
  3. クライアントでJavaScriptをロードしてハイドレーション
  4. React が DOM を「乗っ取り」、インタラクティブになる この方法でも初期表示は高速化できますが、いくつかの問題がありました:

JavaScriptバンドルは依然として大きい

SSRでも、結局クライアントには全てのコンポーネントのJavaScriptコードを送信する必要があります。つまり、初期表示は速くなるけど、ハイドレーションのために大きなJavaScriptファイルをダウンロードする必要があるんです。

サーバーとクライアントの二重実行

SSRでは、同じコンポーネントがサーバーとクライアントの両方で実行されます。これって、よく考えると非効率ですよね。

RSCの革新的なアプローチ

React Server Componentsは、この問題を根本的に解決します:

サーバーでのみ実行されるコンポーネント

Server Componentsは、サーバーでのみ実行され、その結果(特別なシリアライズされた形式)がクライアントに送信されます。クライアントには、これらのコンポーネントのJavaScriptコードが一切送信されません。

サーバーリソースへの直接アクセス

Server Components内では、データベースやファイルシステムに直接アクセスできます。APIルートを介さずにデータを取得できるため、パフォーマンスが大幅に向上します。

Zero Bundle Size

これが一番インパクトある部分ですが、Server Componentsはクライアントのバンドルサイズに影響しません。つまり、どんなに大きなライブラリを使っても、クライアントのパフォーマンスに影響しません。

実際のコードで理解するRSC

シンプルな例から始めよう

理論だけではイメージが湧きにくいので、実際のコードで見てみましょう。僕が初めてRSCを触った時に作ったシンプルな例です。

javascript
// ファイル名: BlogPost.server.js // これはServer Componentです import { db } from './database' import { marked } from 'marked' // Markdownパーサー export default async function BlogPost({ slug }) { // サーバーで直接データベースにアクセス const post = await db.posts.findOne({ slug }) if (!post) { return <div>記事が見つかりません</div> } // サーバーでMarkdownをHTMLに変換 const htmlContent = marked(post.content) return ( <article> <h1>{post.title}</h1> <div dangerouslySetInnerHTML={{ __html: htmlContent }} /> </article> ) }

このコードの何がすごいかというと:

  1. データベースアクセスが直接的: APIルートを作る必要がありません
  2. markedライブラリがクライアントのバンドルに含まれない: サーバーでのみ使用されているから
  3. async/awaitが自然に使える: サーバー環境なので

Client Componentとの組み合わせ

もちろん、全てのコンポーネントをServer Componentにする必要はありません。インタラクティブな機能は依然としてClient Componentで実現します。

javascript
// ファイル名: LikeButton.client.js // これはClient Componentです 'use client' import { useState } from 'react' export default function LikeButton({ initialLikes, postId }) { const [likes, setLikes] = useState(initialLikes) const [isLiking, setIsLiking] = useState(false) const handleLike = async () => { setIsLiking(true) try { const response = await fetch(`/api/posts/${postId}/like`, { method: 'POST' }) const data = await response.json() setLikes(data.likes) } catch (error) { console.error('エラー:', error) } finally { setIsLiking(false) } } return ( <button onClick={handleLike} disabled={isLiking} className="like-button" > 👍 {likes} いいね </button> ) }

そして、これらを組み合わせて使うことができます:

javascript
// ファイル名: BlogPost.server.js import { db } from './database' import { marked } from 'marked' import LikeButton from './LikeButton.client.js' export default async function BlogPost({ slug }) { const post = await db.posts.findOne({ slug }) const htmlContent = marked(post.content) return ( <article> <h1>{post.title}</h1> <div dangerouslySetInnerHTML={{ __html: htmlContent }} /> {/* Client ComponentをServer Component内で使用 */} <LikeButton initialLikes={post.likes} postId={post.id} /> </article> ) }

僕が実際に体験したメリット

1. バンドルサイズの劇的な減少

僕が一番驚いたのは、バンドルサイズの減少でした。従来のNext.jsアプリでは、主要なライブラリがすべてクライアントに送信されていました:

  • 以前: 850KB(gzip後)
  • RSC導入後: 180KB(gzip後) 約78%の減少を実現できました。これは本当にインパクトがありました。

2. 初期表示速度の大幅改善

Lighthouseのスコアで比較してみると:

以前(従来のSSR):

  • First Contentful Paint (FCP): 2.1s

  • Largest Contentful Paint (LCP): 3.4s

  • Time to Interactive (TTI): 4.8s

  • Total Blocking Time (TBT): 450ms RSC導入後:

  • First Contentful Paint (FCP): 1.2s

  • Largest Contentful Paint (LCP): 1.8s

  • Time to Interactive (TTI): 2.1s

  • Total Blocking Time (TBT): 80ms 特にTTI(Time to Interactive)の改善が目覚ましく、ユーザーが「サイトが重い」と感じる時間が大幅に減りました。

3. 開発体験の向上

意外だったのは、開発体験が向上したことです。

APIルートの減少

以前は、データを取得するたびにAPIルートを作っていました:

javascript
// pages/api/posts/[slug].js export default async function handler(req, res) { const { slug } = req.query const post = await db.posts.findOne({ slug }) res.json(post) } // pages/posts/[slug].js export default function PostPage({ post }) { return <BlogPost post={post} /> } export async function getServerSideProps({ params }) { const res = await fetch(`${process.env.BASE_URL}/api/posts/${params.slug}`) const post = await res.json() return { props: { post } } }

RSCでは、この中間レイヤーが不要になりました:

javascript
// app/posts/[slug]/page.js import { db } from '@/lib/database' export default async function PostPage({ params }) { const post = await db.posts.findOne({ slug: params.slug }) return <BlogPost post={post} /> }

コードがシンプルになっただけでなく、メンテナンスするファイル数も減りました。

型安全性の向上

TypeScriptとの組み合わせも素晴らしく、データベースからUIまでの型を一貫して管理できるようになりました。

4. SEOの大幅改善

RSCでは、サーバーで完全にレンダリングされたコンテンツがクライアントに送信されるため、SEOが大幅に改善しました。

Google Search Consoleのデータ:

  • インデックスされたページ数: 85% 向上
  • 平均クリック率: 23% 向上
  • Core Web Vitals: 全項目で「Good」評価

デメリットと注意点

1. 学習コストの高さ

正直に言うと、RSCの理解には時間がかかりました。特に:

メンタルモデルの切り替え

今までの「React = クライアントサイド」という固定観念を手放す必要がありました。最初は「コンポーネントがサーバーで実行されるってどういうこと?」と混乱しました。

デバッグの難しさ

Server Component内でのエラーは、ブラウザのデベロッパーツールではなくサーバーサイドのログで確認する必要があります。最初はこれが結構面倒でした。

2. エコシステムの限定的なサポート

2025年現在でも、RSCをフルサポートしているのは主にNext.jsです。他のフレームワークやライブラリのサポートはまだ限定的です。

ライブラリの互換性

一部のライブラリは、Server Componentでは使用できません。特に:

  • React Contextを使用するライブラリ
  • DOM APIに依存するライブラリ
  • クライアントサイドの状態管理ライブラリ

3. パフォーマンスのトレードオフ

サーバーサイドでの処理が増えるため、サーバーのリソース消費量が増える可能性があります。特に、大量のデータ処理や複雑な計算を行う場合は注意が必要です。

実際の導入体験談