React Server Components vs Client Components
Advertisement
React Server Components vs Client Components
Server Components and Client Components solve different problems. Understanding when to use each is crucial for building efficient applications.
- React Server Components vs Client Components
- Architecture Overview
- When to Use Server Components
- When to Use Client Components
- Mixing Both Patterns
- Performance Implications
- Data Flow Patterns
- Common Mistakes
- Real-World: E-Commerce Page
- FAQ
Architecture Overview
Server Components:
- Execute on server only
- Access databases directly
- Keep secrets server-side
- Reduced JavaScript bundle
- Perfect for data fetching
Client Components:
- Execute in browser
- Can use React hooks
- Handle interactivity
- Access browser APIs
- Smaller data payloads
When to Use Server Components
// Server Component - fetch data
export default async function UserProfile() {
const user = await db.users.findUnique({ where: { id: '123' } })
return <div>{user.name}</div>
}
Use for:
- Pages that fetch data
- Database queries
- Secret API keys
- Large dependencies
- Static content
When to Use Client Components
'use client'
import { useState } from 'react'
export function Counter() {
const [count, setCount] = useState(0)
return <button onClick={() => setCount(count + 1)}>{count}</button>
}
Use for:
- Interactive features
- React hooks
- Event listeners
- Browser APIs
- Real-time updates
Mixing Both Patterns
// Server Component
async function UserDashboard() {
const user = await db.users.findUnique({ where: { id: userId } })
return (
<div>
<h1>{user.name}</h1>
<UserSettings user={user} /> {/* Client Component */}
</div>
)
}
// Client Component
'use client'
function UserSettings({ user }) {
const [settings, setSettings] = useState(user.settings)
return (
<div>
<input value={settings.theme} onChange={(e) => setSettings({...settings, theme: e.target.value})} />
</div>
)
}
Performance Implications
Server Components:
- ✓ No JavaScript for static content
- ✓ Direct database access
- ✓ Secrets stay server-side
Client Components:
- ✓ Instant interactivity
- ✓ Browser API access
- ✗ Larger JavaScript bundle
Data Flow Patterns
Pattern 1: Server Component passes data to Client Component
async function BlogPost() {
const post = await db.posts.findUnique({ where: { id: postId } })
return <CommentForm post={post} />
}
'use client'
function CommentForm({ post }) {
const [comment, setComment] = useState('')
async function handleSubmit() {
await fetch(`/api/posts/${post.id}/comments`, {
method: 'POST',
body: JSON.stringify({ text: comment })
})
}
return (
<form onSubmit={handleSubmit}>
<textarea onChange={(e) => setComment(e.target.value)} />
<button>Submit</button>
</form>
)
}
Pattern 2: Client Component calls Server Action
'use client'
import { updateUserSettings } from '@/app/actions'
import { useState } from 'react'
export function SettingsForm() {
const [theme, setTheme] = useState('light')
const [loading, setLoading] = useState(false)
async function handleChange(newTheme) {
setLoading(true)
await updateUserSettings({ theme: newTheme })
setLoading(false)
}
return (
<select onChange={(e) => handleChange(e.target.value)}>
<option>light</option>
<option>dark</option>
</select>
)
}
Common Mistakes
Mistake 1: Server Component in Client Component
// WRONG
'use client'
async function UserInfo() {
const user = await db.users.find() // Error: can't use async in client
return <div>{user.name}</div>
}
Fix:
// Move to server
export async function UserInfo() {
const user = await db.users.find()
return <div>{user.name}</div>
}
Mistake 2: Non-serializable data
// WRONG
async function Page() {
const date = new Date()
return <ClientComponent date={date} /> // Error: Date not serializable
}
// CORRECT
async function Page() {
const date = new Date().toISOString()
return <ClientComponent date={date} />
}
Real-World: E-Commerce Page
// app/products/[id]/page.tsx (Server)
async function ProductPage({ params }) {
const product = await db.products.findUnique({
where: { id: params.id },
include: { reviews: true }
})
return (
<div>
<ProductDetails product={product} />
<ProductReviews reviews={product.reviews} />
<AddToCart productId={product.id} />
</div>
)
}
// Client Component for interactivity
'use client'
function AddToCart({ productId }) {
const [quantity, setQuantity] = useState(1)
const [added, setAdded] = useState(false)
async function handleAddToCart() {
await fetch('/api/cart', {
method: 'POST',
body: JSON.stringify({ productId, quantity })
})
setAdded(true)
}
return (
<div>
<input type="number" value={quantity} onChange={(e) => setQuantity(Number(e.target.value))} />
<button onClick={handleAddToCart}>{added ? 'Added!' : 'Add to Cart'}</button>
</div>
)
}
FAQ
Q: Can I have Server Components inside Client Components? A: No, but you can pass Server Component results as props.
Q: How do I share state between Server and Client? A: Use URL search params, Server Actions, or data passed as props.
Q: Should all my components be Server Components? A: Yes, by default. Only use 'use client' when necessary.
Mastering the Server/Client component paradigm is essential for modern React development.
Advertisement