When to Use Each
Default to Server Components; reach for Client Components only where you need interactivity.
A simple rule of thumb: start on the server, move to the client only when you must.
Use a Server Component when you...
- Fetch data.
- Access secrets, databases, or the file system.
- Render mostly static content.
Use a Client Component when you...
- Need
useStateoruseEffect. - Handle events like
onClickoronChange. - Use browser-only APIs such as
localStorage.
| Need | Component type |
|---|---|
| Fetch data / DB access | Server |
| State & interactivity | Client |
| Reduce JS shipped | Server |
Example
// Data fetching stays on the server; only the toggle is a client island.
import ThemeToggle from './theme-toggle'; // 'use client'
export default async function Header() {
const user = await getUser(); // server-side
return (
<header>
<span>Hi, {user.name}</span>
<ThemeToggle />
</header>
);
}When to use it
- A developer defaults all new page components to Server Components for better SEO and smaller bundles.
- A form validation component is marked 'use client' only because it needs onChange handlers and useState.
- A data-heavy report page keeps the data table as a Server Component but extracts an export button as a Client Component.
More examples
Server: no interactivity needed
No event handlers or hooks are needed so this stays a Server Component, reducing bundle size.
// app/blog/page.tsx — Server Component (default)
import { getPosts } from '@/lib/posts';
export default async function BlogPage() {
const posts = await getPosts();
return <ul>{posts.map(p => <li key={p.id}>{p.title}</li>)}</ul>;
}Client: needs browser event
The toggle behaviour requires state and a click handler, so 'use client' is necessary here.
'use client';
import { useState } from 'react';
export default function Accordion({ title, body }: { title: string; body: string }) {
const [open, setOpen] = useState(false);
return (
<div>
<button onClick={() => setOpen(o => !o)}>{title}</button>
{open && <p>{body}</p>}
</div>
);
}Hybrid: server page + client widget
Composing both types in one page keeps the heavy data-table server-rendered while the interactive button ships as client code.
// app/dashboard/page.tsx
import StatsTable from './StatsTable'; // Server Component
import ExportButton from './ExportButton'; // 'use client'
export default async function DashboardPage() {
const stats = await fetchStats();
return (
<>
<StatsTable data={stats} />
<ExportButton rows={stats} />
</>
);
}
Discussion