React Server Components introduced a fundamental shift in how we think about component architecture. Yet the 'use client' directive remains one of the most misunderstood aspects of modern React.
Understanding the Mental Model
Server Components (Default):
- Run on the server at request time
- Direct access to databases, file systems, server resources
- Never ship JavaScript to the browser
- Cannot use browser APIs, state, or effects
Client Components:
- Run on the client (also pre-rendered for SSR)
- Can use hooks like useState, useEffect
- Can access browser APIs
- JavaScript is shipped to the browser
Decision Tree
Use this for every component:
- Does it need interactivity? (clicks, form inputs) → Client
- Does it use React hooks? (useState, useEffect) → Client
- Does it need browser APIs? (window, localStorage) → Client
- Does it need real-time updates? (WebSocket, polling) → Client
- None of the above? → Server Component
Composition Patterns That Work
Server Wrapper, Client Islands
// ProductPage.tsx - Server Component
async function ProductPage({ id }) {
const product = await getProduct(id) // Server-side fetch
return (
<article>
<h1>{product.name}</h1>
<p>{product.description}</p>
<AddToCartButton productId={id} /> {/* Client island */}
</article>
)
}Passing Server Components as Children
// Sidebar.tsx - Client Component (needs collapse state)
'use client'
export function Sidebar({ children }) {
const [isOpen, setIsOpen] = useState(true)
return (
<aside className={isOpen ? 'w-64' : 'w-16'}>
<button onClick={() => setIsOpen(!isOpen)}>Toggle</button>
{isOpen && children} {/* Children can be Server Components! */}
</aside>
)
}
// Layout.tsx - Server Component
export function Layout({ children }) {
return (
<div className="flex">
<Sidebar><Navigation /></Sidebar> {/* Server Component passed to Client */}
<main>{children}</main>
</div>
)
}Common Mistakes
Mistake 1: Making everything a Client Component
// ❌ Unnecessary client component
'use client'
export function Footer() {
return <footer>© 2026 Company</footer> // No interactivity needed!
}
// ✅ Keep it as Server Component
export function Footer() {
return <footer>© 2026 Company</footer>
}Mistake 2: Using 'use client' too high in the tree
// ❌ Marking layout as client makes ALL children client components
'use client'
export function DashboardLayout({ children }) {
const [sidebarOpen, setSidebarOpen] = useState(true)
// Now every page in /dashboard is a client component!
}
// ✅ Extract only the stateful part
export function DashboardLayout({ children }) {
return (
<div>
<SidebarToggle /> {/* Client Component - just the toggle */}
<main>{children}</main> {/* Children stay as Server Components */}
</div>
)
}Performance Impact
Server Component page:
- JS sent to client: 0 KB (for component itself)
- Initial HTML: Complete, with data
Client Component equivalent:
- JS sent to client: 15-50 KB
- Initial HTML: Empty or placeholder
- Additional data fetch after hydrationConclusion
Default to Server, opt into Client. Push 'use client' as low as possible. Pass server data as props. Server Components aren't a constraint—they're an opportunity for less JavaScript, faster loads, and simpler data fetching.



