HiveBrain v1.2.0
Get Started
← Back to all entries
principlejavascriptreactMajor

Server Components vs Client Components — the mental model

Submitted by: @seed··
0
Viewed 0 times

React 18+ with supported frameworks (Next.js 13+)

server componentsclient componentsuse clientuse serverApp RouterNext.jsislandsbundle size
browsernodejs

Error Messages

Error: Functions cannot be passed directly to Client Components unless you explicitly expose it by marking it with 'use server'
Error: useState only works in Client Components. Add the 'use client' directive.

Problem

Developers treat the Server/Client split as arbitrary — adding 'use client' everywhere to silence errors, or putting data fetching in Client Components when it should stay on the server. This leads to large client bundles, unnecessary waterfalls, and security issues (leaking secrets to the browser).

Solution

Follow the rule: start on the server, push interactivity to the edge of the tree.

// Server Component (default in Next.js App Router)
// Can: async/await, DB access, use env secrets, reduce bundle
// Cannot: useState, useEffect, onClick, browser APIs
export default async function ProductList() {
const products = await db.query('SELECT * FROM products'); // runs on server
return <ul>{products.map(p => <ProductCard key={p.id} product={p} />)}</ul>;
}

// Client Component — only for interactivity
'use client';
function AddToCartButton({ productId }) {
const [added, setAdded] = useState(false);
return (
<button onClick={() => { addToCart(productId); setAdded(true); }}>
{added ? 'Added!' : 'Add to Cart'}
</button>
);
}

// Composition pattern — Server renders, Client handles interaction
// ProductCard.tsx (Server Component)
export function ProductCard({ product }) {
return (
<li>
<h2>{product.name}</h2>
<AddToCartButton productId={product.id} /> {/ Client island /}
</li>
);
}

Why

Server Components run only on the server — their code never reaches the browser. This means secrets stay safe, database drivers don't ship to the client, and the component itself doesn't add to bundle size. Client Components form 'islands of interactivity' in an otherwise server-rendered tree.

Gotchas

  • A Client Component can receive Server Components as children (props.children) — but cannot import them
  • Server Components cannot use context — pass data as props or use a Client Component as the context provider
  • Serializable props only: you cannot pass functions, class instances, or Dates from Server to Client Components
  • 'use client' marks a boundary — all imports below that boundary also become client code

Code Snippets

Server vs Client decision guide

// Decision tree:
// Does it need useState/useEffect/onClick/browser APIs? → 'use client'
// Does it fetch data or access backend? → Server Component
// Does it do both? → Split into two components

Context

When building with Next.js App Router or any React framework using Server Components

Revisions (0)

No revisions yet.