Topic 3 of 47
Server vs Client Components
Overview
Next.js App Router has two rendering environments. Server Components (default) run only on the server — enabling direct database access and eliminating client-side data fetching. Client Components ('use client') run in the browser and support hooks and browser APIs.
Syntax
tsx
// SERVER COMPONENT (default — no 'use client')
// ✅ Can: fetch data, access DB directly, import server-only packages
// ❌ Cannot: use hooks, event handlers, browser APIs
async function ProductList() {
// Direct DB query — no API needed!
const products = await db.query('SELECT * FROM products LIMIT 20');
return <ul>{products.map(p => <li key={p.id}>{p.name}</li>)}</ul>;
}
// CLIENT COMPONENT
'use client'; // this directive makes it a client component
// ✅ Can: use useState, useEffect, event handlers, browser APIs
// ❌ Cannot: be async, directly access DB
import { useState } from 'react';
function AddToCartButton({ productId }) {
const [loading, setLoading] = useState(false);
return (
<button onClick={() => handleAdd(productId)} disabled={loading}>
{loading ? 'Adding...' : 'Add to Cart'}
</button>
);
}Common Pitfalls
- 'use client' is contagious — it marks the component AND all its imports as client-side. Push it as deep as possible.
- You can import a server component into a client component ONLY if you pass it as children prop — not as a direct import.
- Interview tip: Think of 'use client' as a boundary, not a component type. Everything below that boundary runs on the client.
Real-World Example
Composing server and client components for a product page:
example
tsx
// ProductPage.tsx — SERVER COMPONENT
// Fetches data on server, renders static parts there
async function ProductPage({ params }) {
const product = await getProduct(params.id); // direct DB call
const reviews = await getReviews(params.id);
return (
<div>
{/* Static parts rendered on server */}
<h1>{product.name}</h1>
<p>{product.description}</p>
<img src={product.imageUrl} alt={product.name} />
{/* Interactive parts delegated to client */}
<AddToCartButton productId={product.id} price={product.price} />
<ReviewSection reviews={reviews} productId={product.id} />
</div>
);
}
// AddToCartButton.tsx — CLIENT COMPONENT
'use client';
function AddToCartButton({ productId, price }) {
const [quantity, setQuantity] = useState(1);
const { addItem } = useCart();
return (
<div>
<QuantityInput value={quantity} onChange={setQuantity} />
<button onClick={() => addItem({ productId, quantity, price })}>
Add to Cart (₹{(price * quantity).toLocaleString()})
</button>
</div>
);
}