記事一覧に戻る

React Server Componentsを理解する:次世代のReactアーキテクチャ

5分で読む
Tech Blog Admin
React Server Componentsを理解する:次世代のReactアーキテクチャ
広告スペース
Slot: article-top
Format: horizontal
本番環境でGoogle AdSenseが表示されます

React Server Componentsとは

React Server Components(RSC)は、サーバー側でレンダリングされ、クライアントに送信されるJavaScriptバンドルサイズを削減する革新的な技術です。

従来のSSRとRSCの違い

従来のSSR

// 全てのコンポーネントがクライアントでもハイドレーションされる
function TraditionalSSR() {
  const [count, setCount] = useState(0);
  
  return (
    <div>
      <h1>Count: {count}</h1>
      <button onClick={() => setCount(count + 1)}>
        Increment
      </button>
    </div>
  );
}

React Server Components

// Server Component(デフォルト)
async function PostList() {
  // サーバー側でのみ実行
  const posts = await db.posts.findMany();
  
  return (
    <div>
      {posts.map(post => (
        <PostCard key={post.id} post={post} />
      ))}
    </div>
  );
}

// Client Component
'use client'

function LikeButton({ postId }: { postId: string }) {
  const [liked, setLiked] = useState(false);
  
  return (
    <button onClick={() => setLiked(!liked)}>
      {liked ? '❤️' : '🤍'}
    </button>
  );
}

RSCの主なメリット

1. バンドルサイズの削減

// Server Component - クライアントバンドルに含まれない
import { marked } from 'marked'; // 重いライブラリ
import hljs from 'highlight.js';

async function ArticleContent({ markdown }: { markdown: string }) {
  const html = marked(markdown, {
    highlight: (code, lang) => {
      return hljs.highlight(code, { language: lang }).value;
    }
  });
  
  return <div dangerouslySetInnerHTML={{ __html: html }} />;
}

2. データフェッチングの簡略化

// コンポーネント内で直接データフェッチ
async function UserProfile({ userId }: { userId: string }) {
  const user = await fetch(`/api/users/${userId}`).then(r => r.json());
  const posts = await fetch(`/api/users/${userId}/posts`).then(r => r.json());
  
  return (
    <div>
      <h1>{user.name}</h1>
      <PostList posts={posts} />
    </div>
  );
}

3. 自動的なコード分割

// 動的インポートが自動的に処理される
async function Dashboard() {
  const AdminPanel = await import('./AdminPanel');
  
  return (
    <div>
      <h1>Dashboard</h1>
      <Suspense fallback={<Loading />}>
        <AdminPanel.default />
      </Suspense>
    </div>
  );
}

実践的な実装パターン

パターン1: データフェッチングの最適化

// app/posts/[id]/page.tsx
async function PostPage({ params }: { params: { id: string } }) {
  // 並列データフェッチング
  const [post, comments] = await Promise.all([
    getPost(params.id),
    getComments(params.id)
  ]);
  
  return (
    <article>
      <h1>{post.title}</h1>
      <div>{post.content}</div>
      <Suspense fallback={<CommentsLoading />}>
        <Comments comments={comments} />
      </Suspense>
    </article>
  );
}

パターン2: 混在コンポーネント設計

// Server Component
async function ProductPage({ id }: { id: string }) {
  const product = await getProduct(id);
  
  return (
    <div>
      <h1>{product.name}</h1>
      <p>{product.description}</p>
      <ProductImage src={product.image} />
      {/* Client Componentを含む */}
      <AddToCartButton productId={id} />
    </div>
  );
}

// Client Component
'use client'

function AddToCartButton({ productId }: { productId: string }) {
  const [isAdding, setIsAdding] = useState(false);
  
  const handleAddToCart = async () => {
    setIsAdding(true);
    await addToCart(productId);
    setIsAdding(false);
  };
  
  return (
    <button onClick={handleAddToCart} disabled={isAdding}>
      {isAdding ? '追加中...' : 'カートに追加'}
    </button>
  );
}

ストリーミングとSuspense

// レイアウトでの活用
export default function Layout({ children }: { children: React.ReactNode }) {
  return (
    <div>
      <Suspense fallback={<HeaderSkeleton />}>
        <Header />
      </Suspense>
      <main>{children}</main>
      <Suspense fallback={<FooterSkeleton />}>
        <Footer />
      </Suspense>
    </div>
  );
}

// 段階的なレンダリング
async function SlowComponent() {
  await new Promise(resolve => setTimeout(resolve, 3000));
  return <div>遅いコンポーネント</div>;
}

function Page() {
  return (
    <>
      <FastComponent />
      <Suspense fallback={<Loading />}>
        <SlowComponent />
      </Suspense>
    </>
  );
}

エラーハンドリング

// error.tsx
'use client'

export default function Error({
  error,
  reset,
}: {
  error: Error & { digest?: string }
  reset: () => void
}) {
  useEffect(() => {
    console.error(error);
  }, [error]);
  
  return (
    <div className="error-container">
      <h2>エラーが発生しました</h2>
      <p>{error.message}</p>
      <button onClick={reset}>再試行</button>
    </div>
  );
}

パフォーマンス最適化のコツ

1. 適切なコンポーネント分割

// ❌ 悪い例:全体がClient Component
'use client'
function ProductPage() {
  const [quantity, setQuantity] = useState(1);
  // 静的なコンテンツも含まれる
  return <div>...</div>;
}

// ✅ 良い例:必要な部分のみClient Component
function ProductPage() {
  return (
    <div>
      <ProductInfo /> {/* Server Component */}
      <QuantitySelector /> {/* Client Component */}
    </div>
  );
}

2. データの事前フェッチ

// Parallel data fetching
async function Page() {
  // データフェッチを開始(awaitしない)
  const userPromise = getUser();
  const postsPromise = getPosts();
  
  return (
    <div>
      <Suspense fallback={<UserSkeleton />}>
        <UserInfo userPromise={userPromise} />
      </Suspense>
      <Suspense fallback={<PostsSkeleton />}>
        <PostList postsPromise={postsPromise} />
      </Suspense>
    </div>
  );
}

まとめ

React Server Componentsは、パフォーマンスとDXを両立する革新的な技術です。適切に活用することで、高速でSEOフレンドリーなアプリケーションを構築できます。

Next.js 13以降のApp Routerと組み合わせることで、その真価を発揮します。ぜひプロジェクトで活用してみてください!

広告スペース
Slot: article-bottom
Format: rectangle
本番環境でGoogle AdSenseが表示されます

関連記事