DevToolBox無料
ブログ

React Server Components 完全ガイド 2026

15分by DevToolBox

React Server Components (RSC)はhooks以来最大のReactアーキテクチャの変革です。

React Server Componentsとは?

サーバー上でレンダリングされ、出力をクライアントに送信するコンポーネントです。

RSCモデルには3種類のコンポーネントがあります。

Server vs Client Components

項目Server ComponentClient Component
App Routerデフォルトはいいいえ
ディレクティブなし"use client"
クライアントJSなし(ゼロJS)はい
useState/useEffect利用不可利用可能
イベントハンドラ利用不可利用可能
DB直接アクセスはいいいえ
ファイルシステムはいいいえ
サーバー環境変数はいいいえ
非同期コンポーネントはいいいえ
バンドルサイズゼロ増加

決定木:ServerかClientか?

Should this component be a Server or Client Component?

  Does it need useState, useEffect, or useRef?
  ├── YES → Client Component ("use client")
  └── NO ↓

  Does it need event handlers (onClick, onChange, onSubmit)?
  ├── YES → Client Component ("use client")
  └── NO ↓

  Does it use browser-only APIs (window, localStorage, navigator)?
  ├── YES → Client Component ("use client")
  └── NO ↓

  Does it use third-party libraries that require hooks or browser APIs?
  ├── YES → Client Component ("use client")
  └── NO ↓

  Does it fetch data or access databases/file system?
  ├── YES → Server Component (default) ← BEST CHOICE
  └── NO → Server Component (default) ← still the default

RSCパターン

パターン1:サーバーサイドデータ取得

async/awaitで直接データ取得。

// app/users/page.tsx — Server Component (default, no directive needed)
import { db } from '@/lib/database';

// This component is async — only possible in Server Components
export default async function UsersPage() {
  // Direct database query — this code NEVER reaches the client
  const users = await db.user.findMany({
    orderBy: { createdAt: 'desc' },
    take: 50,
  });

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

// Benefits:
// - Zero JavaScript sent to client for this component
// - No loading states needed (data is available before render)
// - Database credentials never exposed to the browser
// - No useEffect + fetch + useState dance

パターン2:Server-Client合成

ServerはClientをレンダリング可能。

// components/LikeButton.tsx — Client Component
'use client';
import { useState } from 'react';

export function LikeButton({ postId }: { postId: string }) {
  const [liked, setLiked] = useState(false);
  const [count, setCount] = useState(0);

  return (
    <button onClick={() => {
      setLiked(!liked);
      setCount(c => liked ? c - 1 : c + 1);
    }}>
      {liked ? 'Unlike' : 'Like'} ({count})
    </button>
  );
}

// app/posts/[id]/page.tsx — Server Component
import { db } from '@/lib/database';
import { LikeButton } from '@/components/LikeButton';

export default async function PostPage({ params }: { params: { id: string } }) {
  // Server: fetch data
  const post = await db.post.findUnique({ where: { id: params.id } });

  return (
    <article>
      {/* Server-rendered content (zero JS) */}
      <h1>{post?.title}</h1>
      <div>{post?.content}</div>

      {/* Client Component island (only this ships JS) */}
      <LikeButton postId={params.id} />
    </article>
  );
}

// Pattern: Server Component as children of Client Component
// components/Sidebar.tsx
'use client';
export function Sidebar({ children }: { children: React.ReactNode }) {
  const [open, setOpen] = useState(true);
  return (
    <aside style={{ display: open ? 'block' : 'none' }}>
      <button onClick={() => setOpen(!open)}>Toggle</button>
      {children}  {/* Server Component content passed as children */}
    </aside>
  );
}

パターン3:Server Actions

サーバーサイド関数を直接呼び出し。

// app/actions.ts — Server Action
'use server';

import { db } from '@/lib/database';
import { revalidatePath } from 'next/cache';

export async function createPost(formData: FormData) {
  const title = formData.get('title') as string;
  const content = formData.get('content') as string;

  await db.post.create({
    data: { title, content, authorId: 'user-1' },
  });

  revalidatePath('/posts');
}

export async function deletePost(postId: string) {
  await db.post.delete({ where: { id: postId } });
  revalidatePath('/posts');
}

// components/CreatePostForm.tsx — Client Component using Server Action
'use client';
import { createPost } from '@/app/actions';
import { useActionState } from 'react';

export function CreatePostForm() {
  return (
    <form action={createPost}>
      <input name="title" placeholder="Post title" required />
      <textarea name="content" placeholder="Write your post..." required />
      <button type="submit">Create Post</button>
    </form>
  );
}

// Works without API routes!
// The form submits directly to the server action
// Progressive enhancement: works without JavaScript

パターン4:Suspenseストリーミング

遅いデータ取得がページ全体をブロックしない。

// app/dashboard/page.tsx — Streaming with Suspense
import { Suspense } from 'react';

// Slow component — fetches from external API
async function AnalyticsChart() {
  const data = await fetch('https://api.analytics.com/data', {
    next: { revalidate: 60 },
  });
  const analytics = await data.json();
  return <div>Chart: {analytics.visitors} visitors</div>;
}

// Fast component — fetches from local DB
async function RecentPosts() {
  const posts = await db.post.findMany({ take: 5 });
  return (
    <ul>
      {posts.map(p => <li key={p.id}>{p.title}</li>)}
    </ul>
  );
}

export default function Dashboard() {
  return (
    <div>
      <h1>Dashboard</h1>

      {/* This renders immediately */}
      <Suspense fallback={<div>Loading posts...</div>}>
        <RecentPosts />
      </Suspense>

      {/* This streams in when ready (doesn't block the page) */}
      <Suspense fallback={<div>Loading analytics...</div>}>
        <AnalyticsChart />
      </Suspense>
    </div>
  );
}

// Result: User sees "Dashboard" + posts immediately
// Analytics chart streams in when the API responds
// No client-side loading spinners needed

データ取得戦略

逐次データ取得

依存関係がある場合。

// Sequential: second request depends on first
async function UserProfile({ userId }: { userId: string }) {
  const user = await getUser(userId);           // 1st request
  const posts = await getUserPosts(user.name);  // 2nd (depends on user.name)

  return <div>{user.name}: {posts.length} posts</div>;
}

並列データ取得

独立した場合はPromise.all。

// Parallel: independent requests — use Promise.all
async function Dashboard() {
  // Start all requests simultaneously
  const [users, posts, analytics] = await Promise.all([
    getUsers(),
    getPosts(),
    getAnalytics(),
  ]);

  return (
    <div>
      <UserList users={users} />
      <PostList posts={posts} />
      <AnalyticsPanel data={analytics} />
    </div>
  );
}

キャッシュと再検証

Next.jsはfetchにキャッシュ制御を拡張。

// Next.js fetch caching strategies

// 1. Static (cached indefinitely until revalidated)
const data = await fetch('https://api.example.com/data');
// Equivalent to: { cache: 'force-cache' }

// 2. Revalidate every 60 seconds (ISR)
const data = await fetch('https://api.example.com/data', {
  next: { revalidate: 60 },
});

// 3. Dynamic (no caching — fresh on every request)
const data = await fetch('https://api.example.com/data', {
  cache: 'no-store',
});

// 4. Tag-based revalidation
const data = await fetch('https://api.example.com/posts', {
  next: { tags: ['posts'] },
});

// In a Server Action:
import { revalidateTag } from 'next/cache';
revalidateTag('posts');  // Purge all fetches tagged 'posts'

パフォーマンスの利点

RSCは従来のクライアントサイドReactに比べて複数のパフォーマンス優位性を提供。

RSC Performance Benefits:

1. Zero Client JavaScript for Server Components
   - Only Client Components ship JS to the browser
   - Large libraries used on server (markdown, syntax highlighting) add zero bundle

2. Streaming SSR
   - HTML streams to browser as components resolve
   - First Contentful Paint (FCP) is faster
   - Time to Interactive (TTI) is faster (less JS to hydrate)

3. Automatic Code Splitting
   - Each Client Component is automatically code-split
   - No manual dynamic imports needed

4. Reduced Data Transfer
   - Server Components fetch data on the server (same network)
   - No client-side fetch waterfalls
   - No duplicate data in both HTML and JS payload

5. Smaller Hydration Payload
   - Only Client Components need hydration
   - Server Component output is static — no hydration needed

Typical Results:
   Before RSC: 450KB JS bundle, 2.5s TTI
   After RSC:  180KB JS bundle, 1.1s TTI (60% JS reduction)

よくある間違い

// Mistake 1: Adding "use client" unnecessarily
// BAD: This doesn't need client-side JavaScript
'use client';  // REMOVE THIS
export function UserCard({ name, email }: { name: string; email: string }) {
  return <div>{name} ({email})</div>;  // No hooks, no events = Server Component
}

// Mistake 2: Importing Server Component in Client Component
// BAD: This won't work
'use client';
import { ServerComponent } from './ServerComponent';  // ERROR
// FIX: Pass as children instead
export function ClientWrapper({ children }: { children: React.ReactNode }) {
  return <div>{children}</div>;
}

// Mistake 3: Using "use client" too high in the tree
// BAD: Making a layout a Client Component
'use client';  // This makes EVERYTHING below it client-side
export function Layout({ children }: { children: React.ReactNode }) {
  const [theme, setTheme] = useState('dark');
  return <div className={theme}>{children}</div>;
}
// FIX: Extract the interactive part into a small Client Component
// components/ThemeToggle.tsx
'use client';
export function ThemeToggle() {
  const [theme, setTheme] = useState('dark');
  return <button onClick={() => setTheme(t => t === 'dark' ? 'light' : 'dark')}>{theme}</button>;
}
// layout.tsx — stays as Server Component
export function Layout({ children }: { children: React.ReactNode }) {
  return <div><ThemeToggle />{children}</div>;
}

よくある質問

Next.jsなしでServer Componentsは使える?

技術的にはい。実用的にはNext.jsが主要なフレームワーク。

Server ComponentsはSSRの代替?

いいえ、補完します。

"use client"はいつ使う?

hooks、イベントハンドラ、ブラウザAPIが必要な時。

Server ComponentsでContextは使える?

使えません。

SEOへの影響は?

ポジティブ。サーバーでHTMLを生成。

関連ツールとガイド

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

最新情報を受け取る

毎週の開発ヒントと新ツール情報。

スパムなし。いつでも解除可能。

Try These Related Tools

{ }JSON FormatterJSJavaScript Minifier

Related Articles

Next.js App Router: 2026年完全移行ガイド

Next.js App Routerの包括的ガイド。Server Components、データフェッチ、レイアウト、ストリーミング、Server Actions、Pages Routerからの移行手順を解説。

React Hooks完全ガイド:useState、useEffect、カスタムHooks

実践的な例でReact Hooksをマスター。useState、useEffect、useContext、useReducer、useMemo、useCallback、カスタムHooks、React 18+の並行Hooksを学ぶ。

Reactパフォーマンス最適化:15の実践テクニック

React.memo、useMemo等15のテクニックでReactアプリを最適化。