On this page · sections
Server & Client Components
React Server Components (RSC) are the default in the App Router. Server Components run on the server, ship zero component JS, and await data directly. Client Components ("use client") run in the browser and handle interactivity.
| Type | Runs on | Can use | Cannot use |
|---|---|---|---|
| Server Component | Server | async/await, server APIs, direct DB | Hooks, events, browser APIs |
| Client Component | Browser | Hooks, events, browser APIs | async component body, server-only imports |
React Server Components (RSC)
Definition
Server Components render on the server and send a serialized payload to the client — not a JS bundle for the component itself.
// Server Component (default) — no directive needed
async function ProductList() {
const products = await fetchProducts();
return (
<ul>
{products.map((p) => (
<li key={p.id}>{p.name}</li>
))}
</ul>
);
}Benefits:
Zero component JS shipped for server-only UI
Direct server resource access (DB, filesystem, secrets)
Automatic request deduplication within a render pass
Streaming via Suspense boundariesInterview: RSC is not SSR alone — it is a component model where server components never hydrate on the client.
The "use client" directive
Definition
Add "use client" at the top of a file to mark it and all its imports as Client Components.
"use client";
import { useState } from "react";
export function AddToCartButton({ productId }: { productId: string }) {
const [pending, setPending] = useState(false);
return (
<button
disabled={pending}
onClick={() => addToCart(productId)}
>
Add to cart
</button>
);
}Rules:
'use client' is per-file, not per-function
Client Components can import Server Components only as children/slots — not directly
Push 'use client' to the leaves — keep as much tree on the server as possibleReal-world example
Root app/layout.tsx wraps all routes. Client providers go in a thin 'use client' child:
// app/providers.tsx
"use client";
export function Providers({ children }: { children: React.ReactNode }) {
return <ThemeProvider>{children}</ThemeProvider>;
}
// app/layout.tsx
import { Providers } from "./providers";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<Providers>{children}</Providers>
</body>
</html>
);
}Interview: Server Components cannot use hooks or event handlers. Client Components cannot be async or call server-only APIs directly.
Component boundaries
Definition
The server/client boundary is per module. Once a file is "use client", everything it imports becomes part of the client bundle unless dynamically passed from the server.
Server Component
├── can import Server Components directly
├── can render Client Components as children
└── cannot import Client Component modules at top level and use their exports inline
Client Component
├── can import other Client Components
└── receives Server Components only via children prop (composition)// ✅ Server page renders client button
import { AddToCartButton } from "./add-to-cart"; // 'use client' file
export default async function ProductPage({ id }: { id: string }) {
const product = await fetchProduct(id);
return (
<div>
<h1>{product.name}</h1>
<AddToCartButton productId={id} />
</div>
);
}// ❌ Client component cannot import async server component
"use client";
import { ProductList } from "./product-list"; // async server component — errorInterview: The boundary is a one-way door. Server → Client via composition; not Client → Server via import.
Serialization & the RSC payload
Definition
Props passed from Server to Client Components must be serializable — JSON-compatible types that cross the wire.
Allowed → string, number, boolean, null, arrays, plain objects, Date (serialized)
Allowed → React elements passed as children (special case)
Not allowed → functions, class instances, Symbols, Maps/Sets, server-only objects// ✅ Serializable props
<Counter initialCount={0} label="Items" tags={["sale", "new"]} />
// ❌ Cannot pass a function from server to client as a prop
<SearchBox onSearch={(q) => filter(q)} /> // error — functions don't serializeWorkaround — define the callback inside the Client Component or use Server Actions for mutations.
Senior-level usage
Pass IDs and minimal data; let Client Components fetch supplementary client-side data if needed.
// Server
<CommentThread postId={post.id} initialComments={comments} />
// Client — receives plain JSON, manages local UI state
"use client";
export function CommentThread({
postId,
initialComments,
}: {
postId: string;
initialComments: Comment[];
}) {
const [comments, setComments] = useState(initialComments);
// ...
}Interview: RSC props cross a serialization boundary. Functions, classes, and non-plain objects cannot pass through.
Composition patterns
Definition
The children as slots pattern keeps server data fetching in Server Components while nesting interactive Client Components.
// app/product/[id]/page.tsx — Server Component
export default async function ProductPage(props: {
params: Promise<{ id: string }>;
}) {
const { id } = await props.params;
const product = await fetchProduct(id);
return (
<ProductShell product={product}>
<AddToCartButton productId={id} />
<ReviewForm productId={id} />
</ProductShell>
);
}// product-shell.tsx — Server Component
export function ProductShell({
product,
children,
}: {
product: Product;
children: React.ReactNode;
}) {
return (
<article>
<h1>{product.name}</h1>
<p>{product.description}</p>
{children} {/* client islands injected here */}
</article>
);
}Pattern summary:
Server page fetches data → passes to server layout → slots client leaves as children
Provider at root → thin 'use client' wrapper in layout.tsx
Shared interactive UI → one 'use client' module, imported by server pagesInterview: Composition via
childrenis the primary way to mix server data with client interactivity without importing server code into client modules.
Server/client data flow
Definition
Data flows in one direction across the boundary:
Server Component
→ await fetch / DB / unstable_cache
→ pass serializable props to Client Component
→ Client Component manages local UI state
Client Component
→ fetch('/api/...') or React Query for client-side data
→ Server Actions for mutations (form posts, optimistic updates)
→ router.refresh() to bust client Router Cache after mutationsServer Actions bridge mutations back to the server:
"use server";
export async function addToCart(productId: string) {
await db.cart.add(productId);
revalidateTag(`cart-${productId}`);
}"use client";
import { addToCart } from "./actions";
export function AddToCartButton({ productId }: { productId: string }) {
return (
<button onClick={() => addToCart(productId)}>Add to cart</button>
);
}Real-world example
E-commerce product page data flow:
page.tsx (server) → fetch product, reviews (cached ISR)
AddToCartButton → client — calls Server Action
StockBadge → client — polls /api/stock every 30s
layout.tsx (server) → fetch nav categories (shared, cached)Interview: Fetch on the server by default. Client fetch is for live/private data that should not block the initial HTML.
Common footguns
Definition
'use client' at layout level → entire subtree becomes client — avoid
Passing functions as props → serialization error
Importing server code in client → bundle bloat or build error
Async Client Component → not supported — fetch in server parent instead
Secrets in Client Component → exposed in bundle — keep on serverAudit client bundle impact: every "use client" file and its dependency tree ships to the browser. Fetch data in the server parent; keep client modules as small interactive leaves.
Interview: Minimize the client boundary surface. One misplaced
'use client'in a shared layout can balloon the JS bundle.
Senior Interview Summary
RSC
Default server component model. Zero JS for server-only UI; data fetching colocated with render.
"use client"
Marks a module and its imports as client-side. Required for hooks, events, and browser APIs.
Boundaries
Server can render Client via import or children. Client cannot import Server Components directly.
Serialization
Props crossing the boundary must be JSON-serializable. Use children for server-rendered slots.
Data flow
Server fetch → serialize props → client interactivity. Server Actions for mutations.
Best mental model
Server → "Fetch here, ship HTML + RSC payload."
Client → "Interact here, ship JS bundle."
Boundary → "One-way: server composes client, not reverse."Practical rule
Default → Server Component
Need hooks/events → 'use client' at the leaf
Pass data to client → plain objects, IDs, children
Mutations → Server Actions
Live client-only data → client fetch or polling