Skip to main content
← ブログに戻る

React Server Components: A Deep Dive

5 min read著者: pycabbage
Deep DiveFrontend

React Server Components: A Deep Dive

React Server Components (RSC) represent a paradigm shift in how we build React applications. Let's explore how they work, when to use them, and their impact on modern web development.

Understanding Server Components

Server Components run exclusively on the server and send their rendered output to the client. This fundamental difference from traditional React components opens up new possibilities.

Key Characteristics

  • Zero bundle size: Server Components don't add to your JavaScript bundle
  • Direct backend access: Can directly query databases and access server resources
  • Automatic code splitting: Each Server Component is its own chunk
  • Streaming: Can progressively send UI to the client

How Server Components Work

The RSC architecture involves several steps:

  1. Server renders components: React runs on the server
  2. Serialization: Output is serialized to a special format
  3. Streaming: Data streams to the client progressively
  4. Hydration: Client Components hydrate with interactivity
// This is a Server Component (default in Next.js 13+)
async function BlogPost({ id }) {
  // Direct database access - no API needed
  const post = await db.query(`SELECT * FROM posts WHERE id = ${id}`);
  
  return (
    <article>
      <h1>{post.title}</h1>
      <div>{post.content}</div>
      <Comments postId={id} />
    </article>
  );
}

Server vs Client Components

Understanding when to use each type is crucial:

Server Components (Default)

Use for:

  • Static content rendering
  • Data fetching
  • Backend resource access
  • Large dependencies
// Server Component
async function ProductList() {
  const products = await fetch('https://api.example.com/products');
  
  return (
    <div>
      {products.map(product => (
        <ProductCard key={product.id} product={product} />
      ))}
    </div>
  );
}

Client Components

Use for:

  • Interactivity (useState, useEffect)
  • Browser APIs
  • Event handlers
  • Third-party client libraries
'use client';

import { useState } from 'react';

function InteractiveCounter() {
  const [count, setCount] = useState(0);
  
  return (
    <button onClick={() => setCount(count + 1)}>
      Count: {count}
    </button>
  );
}

Data Fetching Patterns

Server Components excel at data fetching:

Parallel Data Fetching

async function Dashboard() {
  // Fetch data in parallel
  const [user, posts, comments] = await Promise.all([
    getUser(),
    getPosts(),
    getComments()
  ]);
  
  return (
    <div>
      <UserProfile user={user} />
      <PostList posts={posts} />
      <CommentSection comments={comments} />
    </div>
  );
}

Sequential Data Fetching

async function UserPosts({ userId }) {
  const user = await getUser(userId);
  const posts = await getPostsByUser(user.id);
  
  return (
    <div>
      <h2>{user.name}'s Posts</h2>
      <PostList posts={posts} />
    </div>
  );
}

Composition Patterns

Mixing Server and Client Components effectively:

// Server Component
async function Article({ slug }) {
  const article = await getArticle(slug);
  
  return (
    <div>
      <h1>{article.title}</h1>
      <p>{article.content}</p>
      {/* Client Component for interactivity */}
      <LikeButton articleId={article.id} />
    </div>
  );
}

// Client Component
'use client';
function LikeButton({ articleId }) {
  const [likes, setLikes] = useState(0);
  
  return (
    <button onClick={() => setLikes(likes + 1)}>
      Like ({likes})
    </button>
  );
}

Performance Benefits

Server Components provide significant performance improvements:

Bundle Size Reduction

// This component and its dependencies stay on the server
import { marked } from 'marked'; // 40KB library
import DOMPurify from 'dompurify'; // 20KB library

async function MarkdownContent({ content }) {
  const html = DOMPurify.sanitize(marked(content));
  
  return <div dangerouslySetInnerHTML={{ __html: html }} />;
}

Streaming and Suspense

import { Suspense } from 'react';

function Page() {
  return (
    <div>
      <Header />
      <Suspense fallback={<LoadingSpinner />}>
        <SlowDataComponent />
      </Suspense>
      <Footer />
    </div>
  );
}

Common Patterns and Best Practices

1. Data Fetching at Component Level

async function ProductDetails({ id }) {
  const product = await getProduct(id);
  
  return (
    <div>
      <h1>{product.name}</h1>
      <RelatedProducts categoryId={product.categoryId} />
    </div>
  );
}

async function RelatedProducts({ categoryId }) {
  const products = await getProductsByCategory(categoryId);
  return <ProductGrid products={products} />;
}

2. Error Boundaries

// error.tsx
'use client';

export default function Error({ error, reset }) {
  return (
    <div>
      <h2>Something went wrong!</h2>
      <button onClick={() => reset()}>Try again</button>
    </div>
  );
}

3. Loading States

// loading.tsx
export default function Loading() {
  return <div>Loading product details...</div>;
}

Limitations and Considerations

Be aware of Server Components limitations:

  1. No browser APIs: Can't use window, document, etc.
  2. No React hooks: useState, useEffect not available
  3. No event handlers: Must delegate to Client Components
  4. Serialization constraints: Props must be serializable

Advanced Patterns

Server Actions

async function Newsletter() {
  async function subscribe(formData) {
    'use server';
    
    const email = formData.get('email');
    await db.insert({ email, subscribedAt: new Date() });
  }
  
  return (
    <form action={subscribe}>
      <input name="email" type="email" required />
      <button type="submit">Subscribe</button>
    </form>
  );
}

Partial Prerendering

export const dynamic = 'force-static';

async function StaticShell() {
  return (
    <div>
      <StaticHeader />
      <Suspense fallback={<Loading />}>
        <DynamicContent />
      </Suspense>
      <StaticFooter />
    </div>
  );
}

Migration Strategy

When adopting Server Components:

  1. Start with leaves: Convert leaf components first
  2. Identify boundaries: Find natural Server/Client boundaries
  3. Optimize data fetching: Move fetching to Server Components
  4. Reduce bundle size: Move heavy dependencies server-side

Conclusion

React Server Components represent the future of React development. They offer unprecedented performance benefits, simplified data fetching, and better developer experience. While they require a mental model shift, the benefits for both developers and users make them worth adopting in modern React applications.

As the ecosystem matures, Server Components will become the default way to build React applications, enabling faster, more efficient web experiences.