Skip to content

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.

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
The server/client 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()
}

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.

What is distinctive about a React Server Component?
How can a Server Component fetch data?
What does the `use client` directive mark?
Which prop can NOT be passed from a Server Component to a Client Component?