Next.js Guide
Server vs Client Components
React Server Components changed how we build React apps. Here's the mental model, the rules, and when to reach for each one.
Quick answer: Default to Server Components — they run on the server, ship no JS, and can fetch data directly. Add "use client" only when you need interactivity, browser APIs, or hooks like useState.
⚡ Quick Comparison
| Feature | Server Component | Client Component |
|---|---|---|
| Runs on | Server | Browser |
| Ships JS to browser | No | Yes |
| Can fetch data directly | Yes (await) | No (use useEffect) |
| useState / useEffect | No | Yes |
| Browser APIs (window, localStorage) | No | Yes |
| Event handlers (onClick) | No | Yes |
| Access to DB / secrets | Yes | No |
| Default in App Router | Yes | No (needs "use client") |
Server Component (Default)
Any component without "use client" is a Server Component:
// app/posts/page.tsx
async function getPosts() {
const res = await fetch('https://api.example.com/posts');
return res.json();
}
export default async function Posts() {
const posts = await getPosts();
return (
<ul>
{posts.map(p => <li key={p.id}>{p.title}</li>)}
</ul>
);
}
Notice: async function, await fetch directly inside, no useEffect, no loading state.
Client Component
Add "use client" at the top when you need interactivity:
// app/components/Counter.tsx
'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 can use hooks, event handlers, and browser APIs.
Mixing Them Together
Server Components can render Client Components as children:
// app/page.tsx — Server Component
import Counter from './components/Counter';
export default async function Home() {
const data = await fetch('https://api.example.com/stats').then(r => r.json());
return (
<div>
<h1>Total users: {data.users}</h1>
<Counter /> {/* Client component inside server page */}
</div>
);
}
The page runs on the server (fast, secure) and passes data into the Counter — which is interactive in the browser.
Passing Server Data to Client
Pass serializable props from server to client:
// app/page.tsx (Server)
import SearchBox from './components/SearchBox';
export default async function Page() {
const categories = await fetch('https://api.example.com/categories').then(r => r.json());
return <SearchBox categories={categories} />;
}
// app/components/SearchBox.tsx (Client)
'use client';
import { useState } from 'react';
export default function SearchBox({ categories }) {
const [query, setQuery] = useState('');
const filtered = categories.filter(c =>
c.name.toLowerCase().includes(query.toLowerCase())
);
return (
<div>
<input value={query} onChange={(e) => setQuery(e.target.value)} />
<ul>{filtered.map(c => <li key={c.id}>{c.name}</li>)}</ul>
</div>
);
}
Server fetches data. Client handles interaction. Best of both.
Passing Server Components as Children
Client Components can't import Server Components directly, but they can receive them as children:
// app/page.tsx (Server)
import Modal from './components/Modal';
import UserList from './components/UserList';
export default function Page() {
return (
<Modal>
<UserList /> {/* Still a Server Component */}
</Modal>
);
}
// app/components/Modal.tsx (Client)
'use client';
export default function Modal({ children }) {
const [open, setOpen] = useState(true);
if (!open) return null;
return (
<div className="modal">
<button onClick={() => setOpen(false)}>Close</button>
{children}
</div>
);
}
The Modal handles interactivity, but the UserList inside it stays a Server Component — it still fetches on the server.
🎯 When to Use Which
Use Server Components when:
- Fetching data from an API or database
- Accessing backend resources or secrets
- Rendering static content (blog posts, product listings)
- Heavy dependencies you don't want in the bundle
- Anything without user interaction
Use Client Components when:
- You need
useStateoruseEffect - You need event handlers (onClick, onChange)
- You need browser APIs (window, localStorage, geolocation)
- You're using a third-party library that uses hooks
- You need React Context
🚫 Common Mistakes
- Adding "use client" everywhere. This defeats the purpose. Only use it where you need interactivity.
- Trying to use useState in a Server Component. Hooks don't work on the server.
- Trying to use window or localStorage in a Server Component. They don't exist on the server.
- Passing functions as props from Server to Client. Only serializable data (strings, numbers, arrays, objects) can cross the boundary.
- Fetching data with useEffect when you don't need to. Server Components fetch directly — no useEffect needed.
- Importing a Client Component into another Client Component's children expecting server behavior. "use client" propagates downward — everything inside becomes a Client Component.
❓ Frequently Asked Questions
What's the difference between Server and Client Components?
Server Components run on the server, ship no JS, and can fetch data. Client Components run in the browser with hooks and event handlers.
Which should I use by default?
Server Components. Only add "use client" when you need interactivity or browser APIs.
What does "use client" do?
Marks a file and its children as Client Components. They get bundled and shipped to the browser.
Can Server Components import Client Components?
Yes. But not the reverse — Client Components can only receive Server Components as children props.
Do Server Components ship JS?
No. Only HTML is sent to the browser, reducing the JavaScript bundle significantly.