ブログ記事

React Server Components完全ガイド - Next.js App Routerで実践

React Server Componentsの概念から実装まで、Next.jsのApp Routerを使った実践的な解説。Client ComponentsとServer Componentsの使い分けやパフォーマンス最適化のテクニックを詳しく解説します。

Web開発
React Next.js RSC フロントエンド パフォーマンス
React Server Components完全ガイド - Next.js App Routerで実践のヒーロー画像

React Server Components(RSC)は、react エコシステムにおける大きなパラダイムシフトです。この記事では、基本概念から実装まで、next.js の App Router を使った実践的な活用方法を詳しく解説します。

この記事で学べること

  • React Server Components の基本概念と仕組み
  • Client Components と Server Components の使い分け
  • データフェッチングのベストプラクティス
  • パフォーマンス最適化のテクニック
  • 実際のアプリケーション構築例

React Server Componentsとは?

React Server Components は、サーバーサイドでレンダリングされる react コンポーネントです。従来の SSR とは異なり、実際にサーバー上でコンポーネントが実行され、結果をクライアントに送信します。

従来のSSRとRSCの違い

チャートを読み込み中...

RSCの主要なメリット

  1. バンドルサイズの削減: サーバーサイドのライブラリがクライアントに送信されない
  2. 初期ページロードの高速化: 必要最小限の javascript のみ送信
  3. SEO最適化: サーバーサイドで実際のコンテンツが生成される
  4. データフェッチの最適化: サーバーから直接データベースアクセス可能

Server ComponentsとClient Componentsの使い分け

React Server Components では、コンポーネントを明確に 2 つのタイプに分ける必要があります。

Server Components(デフォルト)

  • サーバーサイドでのみ実行される
  • データベースやファイルシステムに直接アクセス可能
  • ブラウザ api やイベントハンドラーは使用不可
// app/posts/page.tsx(Server Component)
import { prisma } from '@/lib/prisma'

export default async function PostsPage() {
  // サーバーサイドで直接データベースアクセス
  const posts = await prisma.post.findMany({
    orderBy: { createdAt: 'desc' },
    take: 10
  })
  
  return (
    <div className="posts-container">
      <h1>最新の投稿</h1>
      {posts.map(post => (
        <article key={post.id}>
          <h2>{post.title}</h2>
          <p>{post.excerpt}</p>
        </article>
      ))}
    </div>
  )
}

Client Components(‘use client’宣言が必要)

  • ブラウザで実行される
  • インタラクティブな機能を提供
  • ブラウザ api や state が使用可能
// components/SearchForm.tsx(Client Component)
'use client'

import { useState, useEffect } from 'react'

export default function SearchForm() {
  const [query, setQuery] = useState('')
  const [results, setResults] = useState([])
  
  const handleSearch = async () => {
    const response = await fetch(`/api/search?q=${query}`)
    const data = await response.json()
    setResults(data)
  }
  
  return (
    <div>
      <input
        type="text"
        value={query}
        onChange={(e) => setQuery(e.target.value)}
        placeholder="検索キーワード"
      />
      <button onClick={handleSearch}>検索</button>
    </div>
  )
}

使い分けの基準

Server Component にする場合:

  • データフェッチが主な目的
  • 静的なコンテンツ表示
  • SEO が重要
  • 大きなライブラリを使用

Client Component にする場合:

  • ユーザーインタラクション必要
  • ブラウザ api を使用
  • state 管理が必要
  • イベントハンドラーが必要

データフェッチングのベストプラクティス

React Server Components では、データフェッチングのアプローチが大きく変わります。

1. Server Componentでの直接データフェッチ

従来の方法(useEffect)
Server Component

2. 並列データフェッチングの最適化

// app/dashboard/page.tsx
import { Suspense } from 'react'

// 複数のデータを並列で取得
async function DashboardPage() {
  return (
    <div>
      <h1>ダッシュボード</h1>
      <div className="grid grid-cols-2 gap-4">
        <Suspense fallback={<UserCardSkeleton />}>
          <UserCard />
        </Suspense>
        <Suspense fallback={<StatsCardSkeleton />}>
          <StatsCard />
        </Suspense>
      </div>
    </div>
  )
}

// 各コンポーネントで独立してデータフェッチ
async function UserCard() {
  const user = await getUserData() // 並列実行される
  return <div>{user.name}</div>
}

async function StatsCard() {
  const stats = await getStatsData() // 並列実行される
  return <div>{stats.total}</div>
}

3. キャッシュ戦略の活用

Next.js App Router では、fetch api に強力なキャッシュ機能が内蔵されています。

// デフォルトでは無期限キャッシュ
async function StaticContent() {
  const data = await fetch('https://api.example.com/data')
  return <div>{data}</div>
}

// リクエスト時に重複排除
async function DeduplicatedFetch() {
  // 同じリクエストは自動的にデデュープされる
  const [posts, users] = await Promise.all([
    fetch('https://api.example.com/posts'),
    fetch('https://api.example.com/posts'), // 同じリクエスト
  ])
}
// 時間ベースの再検証
async function TimedRevalidation() {
  const data = await fetch('https://api.example.com/data', {
    next: { revalidate: 3600 } // 1時間ごとに再検証
  })
  return <div>{data}</div>
}

// タグベースの再検証
async function TaggedData() {
  const data = await fetch('https://api.example.com/data', {
    next: { tags: ['posts'] }
  })
  return <div>{data}</div>
}

// Server Actionでの手動再検証
async function updatePost() {
  'use server'
  // データ更新処理
  revalidateTag('posts')
}

パフォーマンス最適化のテクニック

1. Streaming UIとSuspense

// app/posts/[id]/page.tsx
import { Suspense } from 'react'

export default function PostPage({ params }: { params: { id: string } }) {
  return (
    <div>
      {/* 即座に表示される部分 */}
      <PostHeader id={params.id} />
      
      {/* 段階的に読み込まれる部分 */}
      <Suspense fallback={<CommentsSkeleton />}>
        <Comments postId={params.id} />
      </Suspense>
      
      <Suspense fallback={<RelatedPostsSkeleton />}>
        <RelatedPosts postId={params.id} />
      </Suspense>
    </div>
  )
}

async function Comments({ postId }: { postId: string }) {
  // 時間のかかるデータフェッチ
  const comments = await getComments(postId)
  return (
    <div>
      {comments.map(comment => (
        <CommentCard key={comment.id} comment={comment} />
      ))}
    </div>
  )
}

2. レンダリングパフォーマンスの測定

Server Component ペイロード削減 85 %
初期ページロード時間短縮 70 %
JavaScriptバンドルサイズ削減 90 %

実践的なアプリケーション構築例

実際のブログアプリケーションを例に、RSC の活用方法を見てみましょう。

アーキテクチャ設計

ブログアプリケーションのコンポーネント構成

チャートを読み込み中...

ディレクトリ構成

app/
├── layout.tsx                 # Root Layout (Server Component)
├── page.tsx                   # Home Page (Server Component)
├── blog/
│   ├── page.tsx              # Blog List (Server Component)
│   └── [slug]/
│       └── page.tsx          # Blog Post (Server Component)
├── api/
│   ├── posts/
│   └── search/
└── components/
    ├── server/               # Server Components
    │   ├── PostList.tsx
    │   ├── PostCard.tsx
    │   └── PostContent.tsx
    └── client/               # Client Components
        ├── SearchForm.tsx
        ├── CommentForm.tsx
        └── LikeButton.tsx

Server Action の活用

// app/blog/[slug]/page.tsx
import { CommentForm } from '@/components/client/CommentForm'

async function PostPage({ params }: { params: { slug: string } }) {
  const post = await getPost(params.slug)
  
  async function addComment(formData: FormData) {
    'use server'
    
    const content = formData.get('content') as string
    const author = formData.get('author') as string
    
    await prisma.comment.create({
      data: {
        content,
        author,
        postId: post.id
      }
    })
    
    revalidatePath(`/blog/${params.slug}`)
  }
  
  return (
    <article>
      <h1>{post.title}</h1>
      <div>{post.content}</div>
      
      <section>
        <h2>コメント</h2>
        <CommentForm action={addComment} />
      </section>
    </article>
  )
}

よくある問題と解決策

1. Hydration Mismatch

Hydration Mismatch の対処法

Server Component と Client Component の境界で発生しやすい問題です。

  • 時刻や乱数などの動的な値の扱いに注意
  • suppressHydrationWarning の適切な使用
  • useEffect での条件分岐の活用
// 問題のあるコード
function TimeComponent() {
  return <div>現在時刻: {new Date().toLocaleString()}</div>
}

// 修正版
'use client'
function TimeComponent() {
  const [time, setTime] = useState('')
  
  useEffect(() => {
    setTime(new Date().toLocaleString())
  }, [])
  
  return <div>現在時刻: {time}</div>
}

2. データ共有の課題

// Context は Client Component でのみ使用可能
'use client'
function ClientLayout({ children }: { children: React.ReactNode }) {
  return (
    <UserContext.Provider value={user}>
      {children}
    </UserContext.Provider>
  )
}

// Server Component では props で渡す
async function ServerLayout({ children }: { children: React.ReactNode }) {
  const user = await getCurrentUser()
  
  return (
    <ClientLayout user={user}>
      {children}
    </ClientLayout>
  )
}

実装時のチェックリスト

コンポーネント分割の決定

Server/Client の責務を明確化

データフェッチング最適化

並列処理とキャッシュ戦略の実装

パフォーマンス測定

Core Web Vitals の確認

本番環境での検証

実際のユーザー体験の評価

まとめ

React Server Components は、react アプリケーションのパフォーマンスとユーザー体験を大幅に改善する技術です。

Server Components は、サーバーとクライアントの境界を再定義し、より効率的で高速な web アプリケーションの構築を可能にします。

React 開発チーム
React 開発チーム

主要なポイント

  • 明確な責務分離: Server Component と Client Component の使い分けが重要
  • データフェッチの最適化: サーバーサイドでの直接アクセスを活用
  • 段階的な採用: 既存のアプリケーションでも部分的に導入可能
  • パフォーマンス重視: バンドルサイズとレンダリング速度の改善

React Server Components をマスターして、次世代の web アプリケーション開発を始めましょう!

React Server Components 理解度 100 %
完了

この記事は役に立ちましたか?

Daily Hackでは、開発者の皆様に役立つ情報を毎日発信しています。