Next.js App Router: Server Components vs Client Components
In the Next.js App Router (13+), every component is a Server Component by default. This is a big shift from the old Pages Router, and it's the single most misunderstood part of modern Next.js.
The Problem
Most developers coming from Create React App or the Pages Router slap 'use client' at the top of every file "just in case." That defeats the purpose of Server Components: you end up shipping the same amount of JavaScript to the browser as before, just with extra steps.
Server Components (the default)
Server Components render only on the server. No JS for them is sent to the browser. They can await data directly, touch a database, or use secrets safely, because that code never leaves the server.
// app/products/page.tsx
export default async function ProductsPage() {
const res = await fetch('https://api.example.com/products', {
next: { revalidate: 60 },
})
const products = await res.json()
return (
<ul>
{products.map((p: { id: string; name: string }) => (
<li key={p.id}>{p.name}</li>
))}
</ul>
)
}
No useState, no useEffect, no event handlers here — and no client-side fetch waterfall either.
Client Components ('use client')
Client Components are needed for interactivity: hooks, event listeners, browser-only APIs (window, localStorage), or third-party libraries that rely on the DOM.
'use client'
import { useState } from 'react'
export default function LikeButton() {
const [liked, setLiked] = useState(false)
return (
<button onClick={() => setLiked(!liked)}>
{liked ? 'Liked' : 'Like'}
</button>
)
}
The 'use client' directive marks the boundary — everything imported below it also ships to the browser, so keep these components small and push them to the leaves of the tree.
How Rendering Actually Flows
On every request, the flow looks like this:
Two things happen in parallel once the response lands: the static HTML paints right away (fast first contentful paint), and only the Client Component "islands" get hydrated with JS. The Server Component output is never re-executed or hydrated in the browser.
When to Use Which
- Server Component (default): fetching data, reading from a database, using API keys/secrets, rendering static or mostly-static UI, reducing bundle size.
- Client Component (
'use client'):useState/useReducer,useEffect, event handlers (onClick,onChange), browser APIs, context providers, third-party UI libraries that touch the DOM.
Common Pitfalls
- Marking a whole page
'use client'because of one interactive button — push'use client'down to that button only, not the entire page. - Importing a Server Component into a Client Component doesn't work the way you'd expect — pass the Server Component in as
childrenor a prop instead of importing it directly. - Fetching secrets inside a Client Component leaks them into the browser bundle. Keep API keys and DB calls in Server Components only.
- Forgetting
next: { revalidate }onfetchcalls, which silently opts routes into fully static rendering when you actually wanted fresh data.
Default to Server Components everywhere. Only add
'use client'at the smallest leaf component that truly needs interactivity — your bundle size (and your users) will thank you.

