React Server Components vs. Client Components: When to Use Which
A practical guide to choosing between Server Components and Client Components in React and Next.js. Understand the rendering model, performance implications, and real-world patterns for 2026.
React Server Components (RSC) fundamentally changed how we think about rendering in React. Instead of shipping all your component code to the browser, Server Components run exclusively on the server, sending only their rendered output to the client. In Next.js App Router, every component is a Server Component by default.
1. The Mental Model
Think of your component tree as having two zones:
- Server Zone (default): Components that fetch data, access databases, read files, or render static content. They produce HTML on the server and send zero JavaScript to the browser.
- Client Zone (
"use client"): Components that need interactivity — event handlers, state, effects, browser APIs. These hydrate in the browser.
2. When to Use Server Components
Use Server Components when your component:
- Fetches data from a database or API
- Accesses server-only resources (file system, environment variables)
- Renders static or read-only content
- Has large dependencies you don't want in the client bundle
// app/blog/page.tsx — Server Component (default)
import { getBlogPosts } from "@/lib/posts";
export default async function BlogPage() {
const posts = await getBlogPosts(); // Direct DB access
return (
<main>
{posts.map(post => (
<article key={post.id}>
<h2>{post.title}</h2>
<p>{post.excerpt}</p>
</article>
))}
</main>
);
}
3. When to Use Client Components
Add "use client" at the top of a file when your component needs:
- State:
useState,useReducer - Effects:
useEffect,useLayoutEffect - Event handlers:
onClick,onChange,onSubmit - Browser APIs:
window,localStorage,IntersectionObserver - Custom hooks that use any of the above
"use client";
import { useState } from "react";
export default function SearchBar() {
const [query, setQuery] = useState("");
return (
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Search..."
/>
);
}
4. The Composition Pattern
The key insight: Client Components can render Server Components as children. This lets you keep interactivity at the leaves while the majority of your tree stays on the server:
// Server Component (parent)
import InteractiveWidget from "./InteractiveWidget";
export default async function Dashboard() {
const data = await fetchData();
return (
<InteractiveWidget>
{/* These children are Server Components */}
<DataTable data={data} />
<Chart data={data} />
</InteractiveWidget>
);
}
5. Performance Comparison
| Metric | Server Component | Client Component |
|---|---|---|
| JS Bundle Size | 0 KB added | Component code shipped to browser |
| Time to Interactive | Instant (HTML only) | Requires hydration |
| Data Fetching | Direct server access | API calls from browser |
| SEO | Fully rendered HTML | Depends on SSR/hydration |
| Interactivity | None | Full interactivity |
6. Common Mistakes to Avoid
- Don't mark everything as
"use client". Only the components that truly need interactivity. - Don't import Server Components into Client Components. Pass them as
childreninstead. - Don't use
useEffectfor data fetching when a Server Component withasync/awaitworks better. - Don't pass non-serializable props (functions, classes) from Server to Client boundaries.