Server and Client Components
The App Router introduces React Server Components (RSC). By default, every component in app/ is a Server Component unless marked otherwise.
Server Components (Default)
Server Components run on the server during rendering. They:
- Can
async/awaitdata directly - Cannot use hooks (
useState,useEffect) - Cannot use browser APIs (
window,localStorage) - Cannot attach event handlers (
onClick) - Send zero JavaScript to the client for themselves
// app/products/page.tsx — Server Component
async function getProducts() {
const res = await fetch('https://api.example.com/products');
return res.json();
}
export default async function ProductsPage() {
const products = await getProducts();
return (
<ul>
{products.map(p => (
<li key={p.id}>{p.name} — ${p.price}</li>
))}
</ul>
);
}
Client Components
Add 'use client' at the top of a file to make it a Client Component:
'use client';
import { useState } from 'react';
export default function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
);
}
Client Components run in the browser, support hooks and event handlers, and are included in the JavaScript bundle.
When to Use Each
| Use Server Component | Use Client Component |
|---|---|
| Fetch data | Interactive UI (clicks, forms) |
| Access backend resources | State (useState, useReducer) |
| Large dependencies (stay on server) | Effects (useEffect) |
| Static content | Browser-only APIs |
| SEO-critical content | Third-party client libraries |
Rule of thumb: start with Server Components. Add 'use client' only when you need interactivity.
Composition Pattern
Server Components can import Client Components, but not the reverse:
// app/page.tsx — Server Component
import Counter from '@/components/Counter';
import ProductList from '@/components/ProductList';
export default async function Page() {
const products = await getProducts();
return (
<main>
<ProductList products={products} />
<Counter />
</main>
);
}
Pass Server Component output as children to Client Components:
'use client';
export default function Modal({ children }: { children: React.ReactNode }) {
const [open, setOpen] = useState(false);
return (
<>
<button onClick={() => setOpen(true)}>Open</button>
{open && <div className="modal">{children}</div>}
</>
);
}
'use client' Boundary
The directive applies to the entire file and all its imports. Keep Client Components small — push 'use client' as far down the tree as possible:
app/page.tsx Server
├── ProductGrid.tsx Server
│ └── AddToCart.tsx Client (only this ships JS)
└── Footer.tsx Server
Server → Client props must be serializable (JSON-compatible). You cannot pass functions — define event handlers inside the Client Component.
Next: fetch data in Server Components with caching and revalidation.