The single biggest mental shift in Next.js (since v13, refined in v16) is that components are server-rendered by default. You opt into client rendering only where you need it.
| Kind | Marker | Runs On |
|---|---|---|
| Server Component | None (default) | Server only |
| Client Component | "use client" at the top of the file | Server (for the initial HTML) and client (for hydration & interactivity) |
| Shared Component | No client APIs, no server APIs | Either — depends on who imports it |
async directly: export default async function Page() { … }fetch() (deduped & cacheable)What you cannot do:
useState, useEffect, useReducer, useRef — no client hookswindow, document, localStorage)onClick={() => …}What you cannot do:
async (use Suspense + a data hook instead)NEXT_PUBLIC_┌─────────────────────────────────────────┐
│ Server │
│ - Layout (server) │
│ - Page (server, async) │
│ - Hero (server) │
│ - PriceWidget ("use client") ◀──┐ │
│ - <CountUp/> (lib) │ │
│ - Sidebar (server) │ │
│ │ │
└───────────────────────────────────────┼──┘
│
ships JS for this subtree only
Once you mark a component "use client", everything it imports becomes part of the client bundle, transitively. So push the directive as deep as possible.
1// ❌ Marks the whole product card client-side just for a button
2"use client";
3export default function ProductCard({ product }) {
4 return (
5 <article>
6 <Image src={product.cover} alt={product.title} width={400} height={300} />
7 <h3>{product.title}</h3>
8 <p>{product.summary}</p>
9 <button onClick={() => addToCart(product.id)}>Add to cart</button>
10 </article>
11 );
12}1// ✅ Only the button is interactive
2// ProductCard.tsx — server
3export default function ProductCard({ product }) {
4 return (
5 <article>
6 <Image src={product.cover} alt={product.title} width={400} height={300} />
7 <h3>{product.title}</h3>
8 <p>{product.summary}</p>
9 <AddToCartButton id={product.id} />
10 </article>
11 );
12}
13
14// AddToCartButton.tsx — client
15"use client";
16export function AddToCartButton({ id }: { id: string }) {
17 return <button onClick={() => addToCart(id)}>Add to cart</button>;
18}Server Components handle rendering data. Client Components handle rendering interactions.
Most of your tree is Server Components reading data. The interactive leaves are small Client Components: forms, modals, buttons with state, sliders, theme toggles.
| Error | Fix |
|---|---|
Cannot use useState in a Server Component | Add "use client" to the file. |
You're importing a component that needs the browser … | Move the import into a Client Component. |
Functions cannot be passed directly to Client Components | Pass plain JSON-serializable props, or wrap the function in a Server Action. |
Module not found: Can't resolve 'fs' from a Client Component | The component is somehow being included client-side; verify the directive and import chain. |