---
title: "Server vs Client Components"
slug: "server-vs-client-components"
category: "comparisons"
tags: ["comparisons", "react", "server-components", "nextjs", "frontend"]
status: "stable"
last_updated: 2026-05-14
summary: "Default to Server Components for data fetching, SEO, and zero-JS rendering; mark a file 'use client' only when it needs hooks, browser APIs, or event handlers."
related: ["[[frontend/react-server-components]]", "[[frontend/react]]", "[[frontend/nextjs]]", "[[comparisons/nextjs-vs-astro]]", "[[comparisons/react-vs-vue]]", "[[frontend/forms]]"]
---

## Overview

Default every component to Server Component. Add `"use client"` only when the component needs `useState`, `useEffect`, an event handler, or a browser API. The boundary should be as low in the tree as possible: a `ProductPage` is a Server Component that fetches data, an `AddToCart` button inside it is a Client Component. The common mistake is marking the page-level component `"use client"` because one descendant needs interactivity; that pulls the whole subtree to the client and loses every Server Component benefit. See [[frontend/react-server-components]] for the deeper rules.

## When Server Components win

Server Components are the right pick by default. Use them for everything that does not need a hook or a browser API.

- Zero JS to the client: the markup renders on the server and ships as HTML. Bundle size drops by 30 to 70 percent on a typical app.
- Direct database and file system access: `await db.query(...)` inside the component body, no API route in between.
- Secrets stay on the server: API keys and tokens cannot leak to the client because the component never serializes.
- SEO and first paint: the HTML is in the response, not assembled by client JS after hydration.
- Composition: a Server Component can render a Client Component as a child without pulling itself to the client.

## When Client Components win

Client Components are the right pick when the component touches browser-only APIs or holds local interactive state.

- `useState`, `useReducer`, `useEffect`, `useRef`: anything that needs to run between renders or persist across renders.
- Event handlers: `onClick`, `onChange`, `onSubmit` on interactive elements (forms can stay server with Server Actions; see [[frontend/forms]]).
- Browser APIs: `localStorage`, `window`, `navigator`, `IntersectionObserver`, `ResizeObserver`.
- Third-party client-only libraries: charts (Recharts, Visx), editors (Tiptap, Lexical), maps (Mapbox, MapLibre).
- Animation libraries that hook into render lifecycles: Framer Motion, GSAP.
- Anything that previously needed `useLayoutEffect`.

## Trade-offs at a glance

| Dimension         | Server Components            | Client Components                                    |
| ----------------- | ---------------------------- | ---------------------------------------------------- |
| JS shipped        | Zero                         | Full component plus dependencies                     |
| Data fetching     | Direct (`await db.query()`)  | Through fetch or a query lib                         |
| Hooks allowed     | None                         | All React hooks                                      |
| Event handlers    | None on the element          | `onClick`, `onChange`, etc.                          |
| Browser APIs      | No                           | Yes                                                  |
| Secrets           | Stay on server               | Must not appear                                      |
| Serialization     | Renders to RSC payload       | Props must be serializable                           |
| Children boundary | Can render Client Components | Cannot render Server Components except through props |
| Bundle impact     | Zero                         | Adds to client bundle                                |
| Best for          | Data fetching, SEO, layout   | Interactivity, browser APIs                          |

## Migration cost

Moving a Pages Router app to App Router with Server Components is the real migration. Plan it route by route.

- Mark every interactive component `"use client"` first; the build will tell you what is missing.
- Move data fetching from `useEffect` to the Server Component body. Most pages shrink by half.
- Replace `useRouter` from `next/router` with `next/navigation`. The hook signatures differ; budget review time.
- Server Actions replace most API routes; delete the routes as forms migrate. See [[frontend/forms]].
- Realistic budget for a 100-route App Router migration: four to eight engineer-weeks. The win is bundle size and DX, not raw performance for users on broadband.

## Recommendation

- Default to Server Component. Force every contributor to justify adding `"use client"`.
- A button that calls an API: Client Component for the button, Server Action for the call. See [[frontend/forms]].
- A page with a form: Server Component for the page, Client Component for the form inputs (or use Server Actions throughout).
- A dashboard: Server Component for the shell and data; Client Components for interactive widgets only.
- An SPA without SEO needs: still default to Server Components when on Next.js App Router; the boundary discipline is the point. See [[frontend/nextjs]].
- Avoid `"use client"` at the layout level. That pulls the whole route subtree client-side.

## Related

- [[frontend/react-server-components]]
- [[frontend/react]]
- [[frontend/nextjs]]
- [[comparisons/nextjs-vs-astro]]
- [[frontend/forms]]
