Server Components vs Client Components: The Definitive Decision Guide
Back to BlogWeb Development

Server Components vs Client Components: The Definitive Decision Guide

March 16, 20263 min read4 views

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:

  1. Does it need interactivity? (clicks, form inputs) → Client
  2. Does it use React hooks? (useState, useEffect) → Client
  3. Does it need browser APIs? (window, localStorage) → Client
  4. Does it need real-time updates? (WebSocket, polling) → Client
  5. 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 hydration

Conclusion

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.

Share this article