Server Components vs Client Components Explained (2026)
React Server Components (RSC) changed how we build React apps. Components run on the server by default. Client Components run in the browser when you need interactivity. Here's how they work and when to use each.
The Simple Rule
Server Component (default): Runs on the server. No JavaScript sent to the browser. Can directly access databases, APIs, and the file system.
Client Component ('use client'): Runs in the browser. Has state, effects, event handlers, and browser APIs.
Server Component → Static HTML (fast, no JS)
Client Component → Interactive UI (JS required)
How They Work
Server Component (Default in Next.js App Router)
// app/page.tsx — Server Component by default
import { db } from '@/lib/db'
export default async function HomePage() {
// This runs on the server — direct database access!
const posts = await db.select().from(postsTable).limit(10)
return (
<div>
<h1>Latest Posts</h1>
{posts.map(post => (
<article key={post.id}>
<h2>{post.title}</h2>
<p>{post.excerpt}</p>
</article>
))}
</div>
)
}
What happens: Server fetches data, renders HTML, sends it to the browser. Zero JavaScript for this component. Instant load.
Client Component
'use client' // ← This makes it a Client Component
import { useState } from 'react'
export function LikeButton({ postId }: { postId: string }) {
const [liked, setLiked] = useState(false)
const [count, setCount] = useState(0)
const handleLike = async () => {
setLiked(!liked)
setCount(prev => liked ? prev - 1 : prev + 1)
await fetch(`/api/posts/${postId}/like`, { method: 'POST' })
}
return (
<button onClick={handleLike}>
{liked ? '❤️' : '🤍'} {count}
</button>
)
}
What happens: JavaScript is sent to the browser. Component hydrates and becomes interactive.
When to Use Each
Server Components ✅
- Fetching data (database queries, API calls)
- Rendering static content (blog posts, product pages)
- Accessing server-only resources (environment variables, file system)
- Large dependencies (markdown parsers, syntax highlighters — not sent to client)
- SEO-critical content
- Layout and page structure
Client Components ✅
- User interaction (clicks, form inputs, toggles)
- State management (
useState,useReducer) - Effects (
useEffect, subscriptions) - Browser APIs (
window,localStorage,navigator) - Event listeners (
onClick,onChange,onSubmit) - Third-party client libraries (charts, maps, rich text editors)
The Pattern: Server Parent, Client Children
// Server Component (parent)
export default async function ProductPage({ params }) {
const product = await db.query.products.findFirst({
where: eq(products.id, params.id),
})
return (
<div>
{/* Static content — Server Component */}
<h1>{product.name}</h1>
<p>{product.description}</p>
<img src={product.image} alt={product.name} />
{/* Interactive parts — Client Components */}
<AddToCartButton productId={product.id} price={product.price} />
<ReviewSection productId={product.id} />
</div>
)
}
The page structure and data fetching happen on the server. Interactive widgets are Client Components.
Common Mistakes
❌ Making Everything a Client Component
'use client' // Don't add this to every file!
export default function BlogPost({ post }) {
return <article><h1>{post.title}</h1><p>{post.content}</p></article>
}
This component has no interactivity. It should be a Server Component (default).
❌ Fetching Data in Client Components
'use client'
export default function Posts() {
const [posts, setPosts] = useState([])
useEffect(() => {
fetch('/api/posts').then(r => r.json()).then(setPosts) // ❌ Slow
}, [])
}
Fetch data in Server Components instead. No loading state, no waterfall, no client-side fetch.
❌ Passing Functions as Props to Client Components
// Server Component
export default function Page() {
const handleClick = () => console.log('clicked') // ❌ Can't serialize functions
return <ClientButton onClick={handleClick} />
}
Functions can't be passed from Server to Client Components. Use Server Actions instead.
Server Actions (Mutations)
// Server Component with Server Action
export default function CreatePostForm() {
async function createPost(formData: FormData) {
'use server'
const title = formData.get('title') as string
await db.insert(posts).values({ title })
revalidatePath('/posts')
}
return (
<form action={createPost}>
<input name="title" placeholder="Post title" />
<button type="submit">Create</button>
</form>
)
}
No API route needed. The form calls a server function directly.
Performance Impact
| Server Component | Client Component | |
|---|---|---|
| JavaScript sent | 0 bytes | Component code + React |
| Time to Interactive | Instant (HTML) | After hydration (~200ms) |
| Data fetching | Server-side (fast) | Client-side (waterfall) |
| SEO | ✅ Full HTML | ⚠️ Depends on rendering |
Rule of thumb: Every Client Component adds JavaScript to your bundle. Minimize them.
FAQ
Can Server Components use hooks?
No. useState, useEffect, useContext — all require 'use client'. Server Components can't have state or effects.
Can I fetch data in Client Components?
Yes, but prefer Server Components. Use Client Components for data fetching only when you need real-time updates or user-triggered fetches.
Do Server Components work outside Next.js?
Remix supports them experimentally. React's official RSC support requires a framework. Next.js has the most mature implementation.
What about React Context?
Context providers must be Client Components. Wrap them around Server Components that need the context.
Bottom Line
Default to Server Components. Only add 'use client' when you need interactivity (state, effects, event handlers). The pattern: Server Components for data and layout, Client Components for interactive widgets. This gives you the fastest possible pages with interactivity where it matters.