Modern React 16: Server Components and client boundaries
This is part 16 of the Modern React development series.
React Server Components split a React tree across environments. Some components render on the server and never ship their implementation to the browser. Client Components mark the parts that need browser interactivity.
Concept
A Server Component renders in a server environment before the client bundle runs. A Client Component is a component included in the browser bundle because it uses state, Effects, event handlers, browser APIs, or other client-only behavior.
Terms
- RSC: React Server Components, the React architecture for rendering some components in a server environment.
- Server Component: A component rendered by the server-side React environment.
- Client Component: A component included in the browser bundle, usually marked by a framework with
"use client". - Client boundary: The import boundary where a framework starts bundling code for the browser.
Mental model
Think of the server tree as the kitchen and the client tree as the dining table. The kitchen can prepare data-heavy UI without sending its tools to the table. The table needs the interactive pieces the user touches.
How it is used
Use Server Components for data reading, server-only dependencies, content rendering, and static shells. Use Client Components for inputs, event handlers, local state, browser APIs, subscriptions, and interactive widgets.
How to use it
- Start components on the server when using a framework that supports RSC.
- Add
"use client"only at files that need browser interactivity. - Pass serializable props from Server Components to Client Components.
- Keep secrets, database clients, and private environment values on the server side.
- Compose Client Components inside Server Components instead of making a whole route client-only.
Example: Server page with a client filter
// ReportsPage.tsx, Server Componentimport { DateRangeSelector } from "./DateRangeSelector";import { getRevenueReport } from "./reports";
export async function ReportsPage() { const report = await getRevenueReport();
return ( <main> <h1>Revenue report</h1> <p>Total revenue: $ {report.totalRevenue}</p> <DateRangeSelector initialRange={report.defaultRange} /> </main> );}Revenue report
Total revenue: $ 53,000
The page can read server data and render the stable report shell. The range selector can be a Client Component because it responds to browser interaction.
Example: Client boundary for interactivity
"use client";
import { useState } from "react";
type ReportRange = "7d" | "30d" | "90d";
export function DateRangeSelector({ initialRange,}: { initialRange: ReportRange;}) { const [range, setRange] = useState<ReportRange>(initialRange); const options: { label: string; value: ReportRange }[] = [ { label: "7 days", value: "7d" }, { label: "30 days", value: "30d" }, { label: "90 days", value: "90d" }, ];
return ( <fieldset> <legend>Date range</legend> {options.map((option) => ( <button key={option.value} type="button" aria-pressed={range === option.value} onClick={() => setRange(option.value)} > {option.label} </button> ))} </fieldset> );}The directive belongs at the client entry file. Components imported by this file become part of that client bundle path.
Details to watch
- No server directive: Server Components are not marked with
"use server". That directive marks Server Functions. - Serializable props: Values crossing to Client Components need to be serializable by the framework.
- Bundle size: Moving a boundary high in the tree can pull more code into the browser bundle.
- Framework support: RSC requires framework or bundler integration. It is not a standalone client-only React feature.
Series navigation
- Previous: Part 15: Optimistic UI
- Next: Part 17: Server Actions and mutation boundaries
- Series index: Modern React development