Back to Blog

React Server Components: A Practical Guide

Deep dive into React Server Components, understanding when to use them, and how they fit into modern React architecture

3 min read
ReactNext.jsServer ComponentsPerformance

React Server Components (RSC) represent one of the most significant shifts in React's architecture since hooks. Let's explore what they are, why they matter, and how to use them effectively.

What Are Server Components?

Server Components are React components that render exclusively on the server. Unlike traditional server-side rendering (SSR), which sends HTML and then hydrates it on the client, Server Components never send their code to the browser.

tsx
// app/UserProfile.tsx
// This is a Server Component by default in Next.js 13+
async function UserProfile({ userId }: { userId: string }) {
  // This database call happens on the server
  const user = await db.user.findUnique({ where: { id: userId } });

  return (
    <div className="profile">
      <h1>{user.name}</h1>
      <p>{user.bio}</p>
    </div>
  );
}

Key Benefits

1. Zero Bundle Impact

Server Components don't add to your JavaScript bundle. That expensive data-fetching library? Use it freely in Server Components without worrying about bundle size.

2. Direct Backend Access

Access databases, file systems, and other server-only resources directly in your components. No need for API routes as an intermediary.

3. Automatic Code Splitting

React automatically splits Server and Client Components, sending only what's needed to the browser.

When to Use Server vs Client Components

Use Server Components for:

  • Data fetching
  • Accessing backend resources
  • Keeping sensitive information on the server
  • Reducing client-side JavaScript

Use Client Components for:

  • Interactivity (onClick, onChange, etc.)
  • Browser APIs (localStorage, geolocation)
  • State management (useState, useContext)
  • Effects (useEffect)

Real-World Example

Here's a practical example combining both:

tsx
// app/dashboard/page.tsx (Server Component)
import { AnalyticsChart } from "./AnalyticsChart";

async function DashboardPage() {
  // Fetch data on the server
  const analytics = await fetchAnalytics();

  return (
    <div>
      <h1>Dashboard</h1>
      {/* Pass data to Client Component */}
      <AnalyticsChart data={analytics} />
    </div>
  );
}

// app/dashboard/AnalyticsChart.tsx (Client Component)
("use client");

import { useState } from "react";
import { LineChart } from "recharts";

export function AnalyticsChart({ data }: { data: AnalyticsData }) {
  const [timeRange, setTimeRange] = useState("7d");

  return (
    <div>
      <select value={timeRange} onChange={(e) => setTimeRange(e.target.value)}>
        <option value="7d">Last 7 days</option>
        <option value="30d">Last 30 days</option>
      </select>
      <LineChart data={data} />
    </div>
  );
}

Common Pitfalls

1. Trying to Use Client-Only Features

tsx
// ❌ This won't work in a Server Component
function ServerComponent() {
  const [count, setCount] = useState(0); // Error!
  return <button onClick={() => setCount(count + 1)}>{count}</button>;
}

// ✅ Use 'use client' directive
("use client");
function ClientComponent() {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount(count + 1)}>{count}</button>;
}

2. Passing Non-Serializable Props

Server Components can only pass serializable props to Client Components. No functions, class instances, or Date objects.

tsx
// ❌ Can't pass functions
<ClientComponent onClick={() => console.log('hi')} />

// ✅ Define the handler in the Client Component
<ClientComponent />

Conclusion

React Server Components fundamentally change how we think about React applications. By default-ing to server rendering and selectively opting into client interactivity, we can build faster, more efficient applications.

The mental model shift takes time, but the benefits—especially in performance and developer experience—make it worthwhile for modern React development.