Server vs client components: a practical rule

Comparison 9 min read Updated August 2026

The App Router flipped React's default: components now run on the server unless you opt into the client with 'use client'. Most confusion comes from reaching for 'use client' out of habit. Here's a rule you can apply in seconds, plus the boundary mistakes that cause the errors.

The rule

Keep a component on the server by default. Add 'use client' only when the component needs one of these:

  • InteractivityonClick, onChange, and other event handlers.
  • State or lifecycleuseState, useReducer, useEffect.
  • Browser-only APIswindow, localStorage, IntersectionObserver.
  • Client-only libraries — anything that touches the DOM directly.

Everything else — data fetching, reading a database, rendering markup, composing layout — is better on the server. If a component does none of the four things above, it has no reason to ship JavaScript.

Why the default is the server

A Server Component runs once, on the server, and sends HTML with zero component JavaScript to the browser. That means smaller bundles, data fetching next to the database with no exposed keys, and no client-side loading spinner for the first paint.

// Server Component (no directive) — fetches on the server, ships no JS
async function OrderList() {
  const orders = await db.orders.findMany()   // runs on the server
  return <ul>{orders.map((o) => <li key={o.id}>{o.total}</li>)}</ul>
}

There's no useEffect, no loading state, and no API route — the component is the data layer.

Push the boundary down

The most common mistake is marking a whole page 'use client' because one button is interactive. That drags the entire subtree — and its data fetching — to the client. Instead, keep the page on the server and isolate the interactive bit into its own small client component.

// page.tsx — stays a Server Component
import { LikeButton } from "./LikeButton"   // the only client piece

export default async function Post({ id }) {
  const post = await getPost(id)
  return (
    <article>
      <h1>{post.title}</h1>
      <p>{post.body}</p>
      <LikeButton postId={post.id} />        {/* 'use client' lives here */}
    </article>
  )
}

The rule: 'use client' goes on the leaves, not the trunk. Put it as far down the tree as the interactivity actually reaches.

Practice this
Build App Router features for real
Practice React and Next.js challenges — server data, client interactivity, and the seam between them.
Practice React

The boundary mistakes that cause errors

  • Passing a function as a prop across the boundary — a server component can't hand a client component an onClick; only serializable props (strings, numbers, plain objects) cross. Define the handler inside the client component.
  • Using async/await in a client component — client components can't be async. Fetch on the server and pass data down, or use a client data library.
  • Importing a server component into a client component — not allowed. Pass it as children instead, which composes fine.
  • Reaching for window in a server component — it doesn't exist there. That code belongs behind a 'use client' boundary, ideally inside useEffect.

Internalize one line and most of it disappears: server by default, 'use client' only when the browser is genuinely involved, and put the directive on the smallest component that needs it.

FAQ

Are components server components by default in the App Router?

Yes. In the Next.js App Router every component is a Server Component unless the file (or a file it imports from) starts with the 'use client' directive. Server Components render on the server and ship no JavaScript for themselves.

Can a server component import a client component?

Yes — that's the normal pattern. A server component can render a client component and pass it serializable props. The reverse isn't allowed: a client component can't import a server component, but it can accept one as children.

Does 'use client' mean the component only runs in the browser?

No. A client component still renders once on the server for the initial HTML, then hydrates and runs in the browser. 'use client' marks the boundary where interactivity and browser APIs become available.

Put it into practice

Solve real challenges in a full browser IDE — graded automatically.

Browse frameworks