Skip to content
Sahil Durgia/ full-stack
2 min readReact

React Server Components vs Client Components: What Actually Runs Where

'use client' isn't a styling choice — it draws a real line about where code executes, what it can access, and what ships to the browser.

ReactServer ComponentsNext.js

Every component in this series so far ran in the browser. React Server Components (the default in Next.js's App Router — see this site's own components, most of which have no `"use client"` directive) introduced a second place components can run: the server, never shipped to the browser as JavaScript at all.

What actually changes based on where a component runs

  • Server Components can be async functions that directly await a database query or an API call — no client-side loading state needed, because the fetch happens before any HTML is sent.
  • Server Components never ship their JavaScript to the browser at all — their output is HTML (and a compact serialized description React uses to reconcile). Zero client-side bundle cost for a component that's pure, static markup.
  • Server Components cannot use useState, useEffect, or any browser API — there's no client-side render for them to hook into. They render once, on the server, and that's the entire lifecycle.
  • Client Components (`"use client"` at the top of the file) are the familiar model — they run in the browser, can use hooks, can respond to clicks — and their JavaScript does ship to the browser as a real bundle cost.

The actual rule for choosing

Default to a Server Component — it's the default in Next.js's App Router for a reason. Add `"use client"` only at the specific point in the tree where interactivity genuinely starts: a button with an onClick, a form with local input state, anything using a hook. A Server Component can render a Client Component as a child; a Client Component's children are still whatever was passed to it (including Server Components rendered above it in the tree) — the boundary is a line you draw once, not a property that propagates automatically downward through every descendant.

Why this is a real, meaningful architectural decision

This is the actual mechanism behind the "push interactivity to the leaves" advice — a page with one small "Add to cart" button doesn't need to ship React's client runtime for the entire page, just for that one button's subtree. Get the boundary right and most of a page's JavaScript cost disappears; get it wrong (a `"use client"` too high up the tree) and you've silently opted a huge subtree back into full client-side shipping and hydration cost for no reason. This exact tradeoff — and why Next.js is the framework that actually makes it practical to use — is where the next post in this series picks up.

Keep reading
Next: why Next.js exists

Start of the why-Next.js series.