Server Component vs Client Component: when to use which

Status: 🟩 COMPLETE Last updated: 2026-06-21 Plain-English tagline: In Next.js App Router, every file is one or the other. Default to Server; switch to Client only when you genuinely need browser-only features.


What this decides

Every component file in your Next.js App Router project is either:

  • A Server Component β€” runs on the server, can await databases and read secrets, ships zero JavaScript to the browser
  • A Client Component β€” runs in the browser, can use state and event handlers, ships JavaScript to the bundle

You declare a Client Component by putting "use client" at the very top of the file. Without it, you get a Server Component (the default).

For background: Next.js β€” Server vs Client Components 🟩 🟦, Client vs server 🟩.


The short answer

Default to Server Components. Only mark a file "use client" when it genuinely needs one of these:

  • useState / useEffect / useRef (any React hook)
  • An event handler (onClick, onChange, etc.)
  • Browser-only APIs (window, localStorage, document)
  • A Client-Component-only library (e.g. most charting libraries, drag-and-drop)

Most of your app should be Server Components. The interactive bits go in small Client Components.


The factors that matter

  1. Where does the data come from? Database / secret-protected API β†’ Server. User input / browser state β†’ Client.
  2. Does it need state or events? Yes β†’ Client. No β†’ Server.
  3. Does it use browser-only APIs? Yes β†’ Client. No β†’ Server.
  4. How big is the JavaScript bundle impact? Every Client Component adds to the bundle the user downloads. Server Components add zero.
  5. Where does latency matter most? First paint / SEO β†’ Server. Per-click responsiveness β†’ Client.

When to pick Server Component

  • The whole page layout. Headers, navigation, footers, content shells.
  • Anything that reads from the database. Server Components can await Supabase directly with the service_role key β€” no API endpoint needed.
  • Pages that fetch data. Blog posts, dashboards, listings β€” all naturally Server.
  • Static or mostly-static content. Marketing pages, About pages, docs.
  • Anything you want indexed for SEO. The server-rendered HTML is what search engines see.
  • Components that wrap Client Components with data props. A Server Component fetches data β†’ passes to a small Client Component for interactivity.

Bible Quest example: /admin/passages/page.tsx is a Server Component that fetches all passages and renders the schedule list. The β€œdelete” button inside is a tiny Client Component that wraps a <form action={deleteAction}>.


When to pick Client Component

  • Forms with multi-field state. A login form tracking email + password as the user types.
  • Buttons with onClick handlers β€” although Server Actions (<form action={action}>) often replace the need for "use client" here.
  • Animations and transitions (Framer Motion, etc.).
  • Modal dialogs, dropdowns, tooltips β€” anything with open/close state.
  • Real-time UI β€” chat windows, live cursors, websocket-driven views.
  • Charts and data visualizations β€” most charting libraries are Client-only.
  • Anything that uses useEffect to react to changes.

Bible Quest example: QuizClient.tsx is "use client" because it tracks current-question index, selected answer, and shows per-answer feedback animations. All state, no server fetching.


The hybrid pattern (use both)

The strongest Next.js pattern: Server Components fetch data; Client Components handle interaction.

// app/post/[id]/page.tsx β€” SERVER (default)
import { LikeButton } from "./LikeButton";
 
export default async function PostPage({ params }) {
  const post = await db.from("posts").select().eq("id", params.id).single();
  const likeCount = await db.from("likes").count().eq("post_id", post.id);
 
  return (
    <article>
      <h1>{post.title}</h1>
      <p>{post.body}</p>
      <LikeButton postId={post.id} initialCount={likeCount} />
    </article>
  );
}
// app/post/[id]/LikeButton.tsx β€” CLIENT (explicit)
"use client";
import { useState } from "react";
 
export function LikeButton({ postId, initialCount }) {
  const [count, setCount] = useState(initialCount);
  return (
    <button onClick={() => {
      fetch(`/api/like/${postId}`, { method: "POST" });
      setCount(count + 1);
    }}>
      ❀️ {count}
    </button>
  );
}

The Server fetches the data once and passes it as a prop. The Client handles the click. Bundle stays small; data stays close to the database.


Common mistakes

  • Making everything Client out of habit. Old React tutorials all use "use client" (because it’s the only mode in non-App-Router React). For App Router, this is backwards β€” the default is Server.
  • Trying to use useState in a Server Component. TypeScript will complain. Add "use client" or remove the state.
  • Importing a Server Component into a Client Component as anything other than a child. This crosses the boundary the wrong way and Next.js errors. Pass Server Components as {children} instead.
  • Passing functions as props from Server to Client. Functions can’t serialize across the boundary. Define the function in the Client Component, or wrap it as a Server Action.
  • Forgetting "use client" is a directive, not an import. It must be the very first line of the file (after // @ts-check-style comments at most).

What if I’ve already chosen?

β€œI marked it "use client" but I don’t actually need to”: delete the directive. The file becomes a Server Component. Test that everything still renders (you’ll get build errors if it was actually using state/effects).

β€œI want to split a large Client Component to reduce bundle”: identify the parts that don’t need state. Move them to a new Server Component file. Import the Server Component into the Client one β€” works fine.

β€œMy page is a Server Component but I need state on part of it”: extract that part into a new Client Component. Keep the rest as Server.

The boundary is per-file, not per-component, so reorganizing means moving things between files. The cost is low.


See also


Sources