React Server Components: A Primer
Server Components change how we think about React. Here's what you need to know.
The Big Idea
Traditional React: All components run in the browser (client).
Server Components: Some components run on the server only.
Why It Matters
Smaller Bundle
Server Components don't ship their code to the browser.
// This imports a huge library
import { parse } from 'heavy-markdown-parser';
// But it's a Server Component, so the browser never downloads it
async function BlogPost({ slug }) {
const post = await db.posts.find({ slug });
const html = parse(post.content);
return <article dangerouslySetInnerHTML={{ __html: html }} />;
}
The markdown parser stays on the server. Client bundle stays small.
Direct Data Access
No API layer needed:
// Server Component - runs on server
async function UserProfile({ id }) {
const user = await db.users.findUnique({ where: { id } });
return <div>{user.name}</div>;
}
Database query right in the component. No `fetch`, no API route, no loading state.
Streaming
Server Components can stream as they render:
async function Page() {
return (
<div>
<Header /> {/* Instant */}
<Suspense fallback={<Skeleton />}>
<SlowContent /> {/* Streams in when ready */}
</Suspense>
<Footer /> {/* Instant */}
</div>
);
}
Users see content progressively.
Server vs Client Components
Server Components (default in Next.js App Router)
- Can be async
- Can use server-only APIs (database, file system)
- Cannot use hooks (`useState`, `useEffect`)
- Cannot handle events (`onClick`)
Client Components
- Use hooks and state
- Handle user interactions
- Marked with `'use client'` directive
'use client';
function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}
Composition Pattern
Server Components can render Client Components, but not vice versa:
// Server Component
async function ProductPage({ id }) {
const product = await getProduct(id);
return (
<div>
<h1>{product.name}</h1>
<p>{product.description}</p>
{/* Client Component for interactivity */}
<AddToCartButton productId={id} />
</div>
);
}
// Client Component
'use client';
function AddToCartButton({ productId }) {
const [adding, setAdding] = useState(false);
// handle click, show loading, etc.
}
Common Mistakes
Using 'use client' Everywhere
If everything is a Client Component, you lose the benefits.
Start with Server Components. Add `'use client'` only where needed.
Passing Functions as Props
// ❌ Can't pass functions from Server to Client
<ClientComponent onAction={async () => await serverAction()} />
// ✅ Use Server Actions or pass serializable data
<ClientComponent productId={id} />
Getting Started
Next.js 13+ App Router uses Server Components by default. It's the easiest way to try them.