React Server Components changed where React code runs. In frameworks like Next.js with the App Router, components are server components by default, and you opt into the browser with the "use client" directive.

The confusion usually comes from treating this as a performance setting. It is better understood as a question: does this component need the browser?

The mental model

  • Server components run only on the server. They can read databases, files and secrets directly, and their code is never sent to the browser. They cannot use state, effects or event handlers.
  • Client components are rendered on the server for the first HTML and then hydrated in the browser. They can use useState, useEffect, event handlers and browser APIs. Their code is shipped to the browser.

Use a client component when you need

  • Interactivity: onClick, onChange, form input state.
  • React state or effects: useState, useReducer, useEffect.
  • Browser APIs: localStorage, window, geolocation.
  • Libraries that depend on any of the above.

Everything else can stay on the server.

Push "use client" to the leaves

"use client" marks a boundary: that module and everything it imports become client code. Put the boundary as low in the tree as possible.

tsx
// app/products/[id]/page.tsx  (server component)
export default async function ProductPage({ params }) {
  const product = await db.product.find((await params).id); // runs on the server
  return (
    <article>
      <h1>{product.name}</h1>
      <p>{product.description}</p>
      <AddToCartButton productId={product.id} /> {/* only this is client code */}
    </article>
  );
}
tsx
// AddToCartButton.tsx
"use client";
export function AddToCartButton({ productId }) {
  const [pending, setPending] = useState(false);
  return <button disabled={pending} onClick={() => addToCart(productId)}>Add to cart</button>;
}

Passing server content into client components

Client components can receive server components as children or props. This keeps heavy content server-rendered inside an interactive wrapper.

tsx
<Tabs>            {/* client: manages active tab */}
  <ProductSpecs /> {/* server: rendered on the server, passed as children */}
</Tabs>

Common mistakes

  • Marking a whole page "use client" because one button needs state. Extract the button instead.
  • Passing non-serialisable props (functions, class instances) from server to client components. Props must be serialisable, except for Server Actions.
  • Importing server-only code into client components, which can leak secrets or break the build. Mark such modules with import "server-only".
  • Fetching in a client effect what could be fetched on the server, causing loading spinners and extra requests.

Key takeaways

  • Server by default; use client only when the component needs the browser.
  • "use client" is a boundary, so put it at the leaves.
  • Pass server-rendered content into client components as children.
  • Keep secrets and data access in server-only modules.