React Server Components: A Practical Guide
What Are React Server Components?
React Server Components (RSC) represent a fundamental shift in how we build React applications. They allow components to run exclusively on the server, reducing JavaScript sent to the client and enabling direct access to backend resources.
The Mental Model
Think of your React tree as having two types of components:
// Server Component (default in Next.js App Router)
// Runs on the server, zero JS sent to client
async function ProductList() {
const products = await db.products.findMany();
return (
<ul>
{products.map(p => (
<li key={p.id}>{p.name}</li>
))}
</ul>
);
}
// Client Component
// Runs on client, enables interactivity
'use client';
function AddToCartButton({ productId }: { productId: string }) {
const [loading, setLoading] = useState(false);
return (
<button onClick={() => addToCart(productId)}>
Add to Cart
</button>
);
}When to Use Server Components
Server Components excel when:
- Fetching data - Direct database/API access without client-side state
- Accessing backend resources - File system, environment variables
- Keeping sensitive logic server-side - API keys, business logic
- Reducing bundle size - Large dependencies stay on server
When to Use Client Components
Reach for Client Components when you need:
- Interactivity - onClick, onChange, form submissions
- Browser APIs - localStorage, geolocation, intersection observer
- State - useState, useReducer, context
- Effects - useEffect, custom hooks with effects
The Composition Pattern
The key insight is that Server Components can import and render Client Components, but not vice versa. This creates a natural boundary:
// Server Component
import { AddToCartButton } from './AddToCartButton';
async function ProductCard({ id }: { id: string }) {
const product = await getProduct(id);
return (
<div>
<h2>{product.name}</h2>
<p>{product.description}</p>
{/* Client Component for interactivity */}
<AddToCartButton productId={id} />
</div>
);
}Common Patterns
Pattern 1: Data fetching at the top
// page.tsx (Server Component)
async function Page() {
const data = await fetchData();
return <ClientForm initialData={data} />;
}Pattern 2: Passing Server Data as Props
// Server Component fetches, Client Component displays/interacts
async function Dashboard() {
const metrics = await getMetrics();
return <MetricsChart data={metrics} />;
}Pattern 3: Streaming with Suspense
import { Suspense } from 'react';
function Page() {
return (
<div>
<Header />
<Suspense fallback={<LoadingSkeleton />}>
<SlowComponent />
</Suspense>
</div>
);
}Performance Implications
Server Components provide significant performance benefits:
- Smaller bundles - Server-only code never ships to client
- Faster initial load - HTML streams immediately
- Better SEO - Content rendered server-side
- Reduced waterfall - Data fetching happens server-side
Conclusion
React Server Components aren't a replacement for Client Components. They're a powerful addition that lets you choose the right rendering strategy for each component. Start with Server Components by default, and add the 'use client' directive only when you need interactivity.