Server Components
A component that never reaches the browser
Section titled “A component that never reaches the browser”A React Server Component (RSC) runs on the server, produces its output there, and never ships its code to the client. The browser receives the result, not the component. That has two big payoffs: the component’s dependencies (a Markdown parser, a database client) stay off the client bundle, and the component can access server resources directly.
Because it runs on the server, a Server Component can be async and fetch data inline — no useEffect, no loading state, no client round-trip:
// Server Component — runs on the server. Note: it's an async function.export default async function ProductPage({ id }) { const product = await db.products.findById(id); // direct DB access, on the server return ( <article> <h1>{product.name}</h1> <p>{product.description}</p> </article> );}There is no 'use client' here, so this is a Server Component. It awaits data and returns JSX; the client only ever sees the rendered markup.
The server/client boundary: 'use client'
Section titled “The server/client boundary: 'use client'”Server Components can’t do interactive things — no useState, no onClick, no browser APIs, because that code never runs in the browser. When you need interactivity, you cross into a Client Component by putting the 'use client' directive at the top of the file:
'use client';import { useState } from 'react';
// Client Component — ships to the browser, can use state and events.export default function AddToCart({ productId }) { const [count, setCount] = useState(0); return <button onClick={() => setCount((c) => c + 1)}>In cart: {count}</button>;}'use client' marks the boundary: that file and everything it imports become client code. Server Components can render Client Components (a server page can include an interactive button), but not the reverse in the same way — the direction of the boundary matters.
flowchart TB
subgraph server["Server (never shipped)"]
sc["Server Component
async, DB access, secrets"]
end
subgraph client["Client (shipped to browser)"]
cc["Client Component
use client, useState, onClick"]
end
sc -->|renders + passes
serializable props| cc What can cross the boundary
Section titled “What can cross the boundary”A Server Component passes props to a Client Component, but those props must be serializable — they have to survive being sent over the network as data:
- ✅ Strings, numbers, booleans, plain objects/arrays, JSX, and promises (the client resolves them with
use()). - ❌ Functions (event handlers), class instances, or anything holding live state — these can’t be serialized and sent.
// Server Component passes a promise across the boundary:export default function Page() { const commentsPromise = fetchComments(); // not awaited here return <Comments commentsPromise={commentsPromise} />; // Client resolves it with use()}RSC comes from a framework
Section titled “RSC comes from a framework”One honest caveat: you don’t get Server Components from bare react + a bundler. RSC needs a framework or build setup that understands the server/client split and can serialize the boundary — in practice, Next.js (App Router), and other RSC-capable frameworks. The concepts here are React’s; the wiring is the framework’s.