IHOR.DEVKNOWLEDGE_BASE
MENU

~/blog / Web Stack / react-server-components-vs-client-components.md

// pillar guide · 4 min read

React Server Components vs Client Components

Place React Server and Client Component boundaries by execution needs, imported dependencies, serializable props, and interactive state in the Next.js App Router.

Quick answer

Use Server Components for server-side reads and noninteractive rendering; use Client Components for state, event handlers, effects, and browser APIs. The use client directive creates a module dependency boundary, not merely a visual label on a component. Keep the boundary close to the interactive control and pass the smallest data projection it needs. This leaves noninteractive work outside the browser bundle.

Practical implementation steps

1. Identify the actual browser capability

A title and formatted description need no client state. A disclosure button needs an event handler and local state. A location control needs a browser API and permission handling. Mark the smallest module that owns those capabilities. Putting use client on a page merely because one child is interactive can pull formatting libraries and unrelated dependencies into the client graph.

2. Design the crossing data contract

React must serialize values crossing from server-rendered code to client code. Supported values are broader than plain JSON, but arbitrary functions and class instances are not general-purpose client props. Pass public fields instead of an entire database row, token, or privileged adapter. An opaque ID can identify an operation; the server must still authenticate and authorize that operation when it runs.

3. Compose server output through client slots

A Server Component can render content and pass it as children to a Client Component wrapper. The wrapper does not import that server module, so its import graph does not absorb the server dependency. This is useful for a modal shell around server-rendered content. Composition does not make server data private: any rendered output or props delivered to the browser can be inspected by the user.

4. Account for initial rendering

A Client Component can be prerendered into initial HTML before hydration. Do not read window or localStorage during its render simply because it has use client. Read browser-only state in an effect or event where appropriate and provide a stable initial display. A difference between server and browser output can cause hydration errors; hiding those warnings does not repair the contract.

Implementation example

This control is purely local: it does not claim to persist a favorite. A server page can pass public initial data while keeping its own read logic on the server. Add a server mutation only if the product requires persistence, then include pending, rejected, and repeated-request behavior. Keep the displayed value distinct from an authoritative stored preference.

// app/components/favorite-toggle.tsx
"use client";

import { useState } from "react";

export function FavoriteToggle({ initial }: { initial: boolean }) {
  const [selected, setSelected] = useState(initial);
  return (
    <button type="button" aria-pressed={selected}
      onClick={() => setSelected((previous) => !previous)}>
      {selected ? "Remove favorite" : "Add favorite"}
    </button>
  );
}

Tradeoffs and alternatives

Client state is appropriate for immediate interaction, temporary selections, and browser capabilities. Server rendering is appropriate for reading privileged systems and removing noninteractive dependencies from the browser. A provider can share client state across controls, but wrapping the entire document in it is rarely necessary. Choose state scope from the interaction; a global store is not required merely because several Server Components appear on one page.

Common mistakes

Do not import a database helper into a client component and assume a server filename protects it. Do not pass secrets as props because the source component runs on the server. Do not call an ordinary async Server Component directly from a client module. Finally, server rendering does not remove authorization requirements, and client-side hiding does not prevent direct invocation of a server endpoint.

Production checklist

  • Each use client directive has a browser capability reason.
  • Props expose only intended public values.
  • Browser-only reads occur outside prerendering.
  • Imported dependencies match the module environment.
  • Interaction is keyboard accessible and has clear state.
  • Persistent writes independently verify permission.

FAQ

Is use server the opposite of use client?

No. use server marks Server Functions; it is not how ordinary Server Components are declared. In the App Router, unmarked pages and layouts are Server Components by default. Adding a mutation boundary is a different decision from choosing the rendering environment.

Can a client wrapper contain server-rendered content?

Yes, when a server parent supplies rendered content through children or another prop. The important distinction is composition from a server parent versus importing the server module inside the client module.

Next step

Draw the client import graph for one interactive route. Move the directive toward the actual controls, inspect crossing props for private fields, and verify initial HTML matches hydration. Measure the resulting browser output before claiming a performance gain from the refactor.

Primary sources

Ihor Kharchyshyn

Independent full-stack product engineer writing source-backed implementation guides for browser extensions and modern web products.

Need this implemented in a real product?

Explore relevant product case studies or share the context for a web application, Chrome extension, API, or SaaS build.