Summary
React Server Components (RSC) run only on the server — they fetch data, render to a wire format, and send the result to the client without bundling their code into the JS payload. Static subtrees add no client JS; interactive parts opt in with `"use client"`. Default in the Next.js App Router.
Jump to the interview angleRSC splits the component tree into two worlds: server components run on the server (or at build time), have direct access to databases and secrets, and emit React's wire format — not HTML. Client components, marked "use client", are the interactive islands that hydrate in the browser.
The boundary is explicit: any component that touches useState, useEffect, or browser APIs must be a client component. Props crossing the boundary must be serializable — strings, numbers, plain objects, arrays — not functions or class instances.
Large, data-heavy subtrees stay server-side, shrinking the JS bundle the browser must parse and execute.
Request lifecycle
- 1
Server render
Next.js renders the server component tree on the server. Each
asynccomponentawaits its own data — DB queries, ORM calls, or internal fetches — directly, with no API route needed. - 2
RSC payload
React produces an RSC payload — a compact wire format encoding the component tree and streamed data — not HTML. This is sent to the client alongside the HTML shell.
- 3
Client reconcile
React's runtime on the client reconciles the payload into the existing DOM. Server components never re-render in the browser; only
"use client"subtrees hydrate. - 4
Props serialization
Props flow one-way: server to client, serialized in the payload. Client components can import server components only as props (children or slots), not as direct imports — the bundler enforces this.
Server component with a client island
The server component fetches data directly; AddToCartButton is the only code shipped to the browser. Props passed across the boundary are plain serializable values.
// app/products/[id]/page.tsx — server component (no "use client")
export default async function ProductPage({
params,
}: {
params: Promise<{ id: string }>;
}) {
const { id } = await params;
// Runs on the server — never exposed to the browser.
const product = await db.product.findUnique({ where: { id } });
return (
<article>
<h1>{product.name}</h1>
<p>{product.description}</p>
{/* Client island — only this subtree ships JS */}
<AddToCartButton productId={product.id} price={product.price} />
</article>
);
}
// components/AddToCartButton.tsx
"use client"; // marks the server/client boundary
import { useState } from "react";
export function AddToCartButton({
productId,
price,
}: {
productId: string;
price: number;
}) {
const [added, setAdded] = useState(false);
return (
<button onClick={() => setAdded(true)}>
{added ? "Added!" : `Add — $${price}`}
</button>
);
}db never reaches the browser. "use client" is the boundary directive — everything above it stays server-only. Props are serializable strings and numbers.
Tradeoffs
Pros
- Zero JS shipped for server-only components — smaller bundles, faster parse.
- Data fetching co-located with the component; no API route required.
- DB credentials and secrets stay on the server by default.
- Async server components eliminate client-side data-fetching waterfalls.
Cons
- Props crossing the boundary must be serializable — no functions.
- No `useState`, `useEffect`, or browser APIs inside server components.
- Two component types add mental overhead for teams new to RSC.
- Server render and client hydration are separate phases — harder to debug.
Common pitfalls
Passing non-serializable props (functions, class instances) across the boundary crashes at runtime. Marking too many components "use client" negates bundle savings — keep islands small and at the leaves. Context providers must be client components, so server components cannot consume React context. In Next.js 15+, params and searchParams are async Promises and must be awaited.
Interview angle
Interviewers test whether you understand the boundary and its constraints. Know why props must be serializable, how the RSC payload differs from HTML, and when to reach for a client island.
Soundbite: "Server components run once on the server, ship zero JS, and fetch data directly. Client components are the interactive islands — keep them small and at the leaves."
Key terms
- RSC payload
- React's compact wire format encoding the server-rendered component tree, streamed to the client.
- "use client"
- Directive marking the file as a client component boundary; bundler includes it in the JS bundle.
- Serializable props
- Props that survive JSON serialization: strings, numbers, plain objects, arrays — not functions.
- Client island
- A `"use client"` subtree embedded inside a server-rendered tree that hydrates independently.