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
awaitdatabases 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
- Where does the data come from? Database / secret-protected API β Server. User input / browser state β Client.
- Does it need state or events? Yes β Client. No β Server.
- Does it use browser-only APIs? Yes β Client. No β Server.
- How big is the JavaScript bundle impact? Every Client Component adds to the bundle the user downloads. Server Components add zero.
- 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
awaitSupabase directly with theservice_rolekey β 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
useEffectto 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
useStatein 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
- Next.js π© π¦ β the framework
- Client vs server π© β the foundational distinction
- Server Actions π© β often replaces βI need a Client Component just for this buttonβ
- React π©
- SPA vs MPA vs SSR vs SSG π© β broader rendering-strategy context
- Browser errors β Hydration failed π© β the most common bug when the boundary is wrong