What you'll learn
- Server Components by default for data and static UI
- Client Components for interactivity, browser APIs, and local state
- Pass serializable props across the boundary
- Checklist before marking a file client
Direct answer: keep components on the server until they need state, effects, or browser APIs. Mark that leaf (or a small island) with "use client". Do not make the whole app a Client Component tree by default.
Deep rendering modes: React Rendering Strategies. Decision of SSR/SSG/ISR/CSR: SSR, SSG, ISR, or CSR.
When to use Server Components
- Fetching data close to the source without shipping a client waterfalls of fetch code
- Large static or mostly static UI
- Keeping secrets and heavy dependencies off the client bundle
When to use Client Components
useState,useReducer,useEffect- Event handlers (
onClick, forms with client validation UX) - Browser-only APIs (
window,localStorage, observers)
When not to force either
- A pure presentational component with no hooks can stay a Server Component and be imported by both
- Do not wrap the root layout in
"use client"just to use one interactive widget
Practical example
TSX
// app/posts/page.tsx (Server Component)
import { LikeButton } from './like-button'
export default async function PostsPage() {
const posts = await db.posts.list()
return (
<ul>
{posts.map((post) => (
<li key={post.id}>
<h2>{post.title}</h2>
<LikeButton postId={post.id} />
</li>
))}
</ul>
)
}TSX
// app/posts/like-button.tsx
'use client'
import { useState } from 'react'
export function LikeButton({ postId }: { postId: string }) {
const [liked, setLiked] = useState(false)
return (
<button type="button" onClick={() => setLiked(true)}>
{liked ? 'Liked' : 'Like'} {postId}
</button>
)
}Comparison
| Concern | Server Component | Client Component |
|---|---|---|
| Runs where | Server (per request/build) | Browser (+ SSR hydrate) |
| Hooks | No | Yes |
| Bundle cost | Not shipped as JS to client | Shipped |
| Secrets / DB | OK | Never |
Common mistakes
"use client"at the top of every file- Passing non-serializable props (functions, class instances) across the boundary
- Fetching the same data again on the client after the server already had it
Checklist
- Does this file need state or effects? If no, keep it server.
- Is the client boundary as low in the tree as possible?
- Are props across the boundary serializable?
- Did you measure bundle impact after adding a client island?
Related
Final recommendation
Default to Server Components. Push "use client" to interactive leaves. That keeps HTML fast and JavaScript smaller.