React Server Components (RSC) are one of the most significant changes to React in years. They are also one of the most misunderstood. This guide explains what they actually are, what problems they solve, and when to choose them over other rendering approaches.
What RSC Actually Is
A Server Component is a React component that runs exclusively on the server. It is never sent to the client as JavaScript. The server renders it to HTML (or a serialized RSC payload), sends the result to the browser, and the browser displays it without needing the component code.
The critical implication: a Server Component contributes zero bytes to your JavaScript bundle. If you have a component that renders a data table, that component's logic (fetching, sorting, formatting) stays on the server. The user's browser never downloads it.
This is different from Server-Side Rendering (SSR). With traditional SSR, the component runs on the server to generate HTML, but the same JavaScript is also sent to the browser for hydration. With RSC, there is no hydration for the server component itself. It just renders and is done.
Why It Matters
Zero bundle cost. The most concrete benefit. A library used only in a Server Component does not appear in the client bundle. A component with complex formatting logic contributes nothing to the JavaScript the user downloads and parses.
Direct data access. Server Components can access databases, file systems, and internal services directly. No API route needed. You write a database query in the component body, and it executes on the server with no client involvement.
// This component queries MongoDB directly. None of this code reaches the browser.
async function UserProfile({ userId }: { userId: string }) {
const db = await getDatabase();
const user = await db.collection("users").findOne({ _id: new ObjectId(userId) });
if (!user) return <div>User not found</div>;
return <div>{user.name}</div>;
}
No waterfall for initial data. Client-side data fetching with useEffect causes a waterfall: render empty shell, ship JavaScript to browser, browser executes component, component fires fetch, data arrives, rerender. Server Components eliminate this for initial page data. The data is fetched during the server render, and the populated HTML arrives in the first response.
The Client/Server Boundary
The boundary between server and client components is controlled by the "use client" directive. A file with "use client" at the top becomes a client component. Everything else is a server component by default in the App Router.
The key constraint: props passed from a Server Component to a Client Component must be serializable. You cannot pass functions, class instances, or non-serializable objects across the boundary. You can pass strings, numbers, plain objects, and arrays.
// Server Component -- fine
async function Page() {
const data = await fetchData();
return <ClientChart data={data} />; // data must be serializable
}
// Client Component
"use client";
function ClientChart({ data }: { data: ChartData[] }) {
const [selected, setSelected] = useState(null);
// interactivity here
}
The pattern: Server Component fetches data and passes serializable data to Client Components that handle interactivity.
Common Patterns
Data fetching in server, interactivity in client. This is the core pattern. The server component owns the data layer. The client component owns the interaction layer. They meet at a clean boundary where serializable data is passed as props.
Async components. Server Components can be async functions. You await database queries, API calls, or file reads directly in the component body. This is not possible in client components.
Composition. You can import and render Server Components inside Client Components... but only if you pass them as children or props from outside, not import them inside a client component file. This is the pattern for wrapping client context providers around server component trees.
What RSC Does Not Help With
Interactive components still need "use client". Anything with onClick, onChange, useState, useEffect, or browser APIs must be a client component. RSC does not change this.
Real-time data still needs a client-side solution. RSC fetches data at render time on the server. For data that changes after the page loads (live dashboards, chat messages, notifications), you still need SWR, TanStack Query, WebSockets, or Server-Sent Events running on the client.
Components that depend on browser APIs. localStorage, window, navigator, and similar APIs are not available in Server Components. Move that logic to client components.
The Suspense Integration
RSC integrates with Suspense for streaming. When a Server Component is wrapped in Suspense, Next.js can stream the HTML for the rest of the page while waiting for the suspended component's data. The shell of the page arrives fast, and the data-dependent sections stream in as they resolve.
export default function Page() {
return (
<div>
<Header /> {/* Renders immediately */}
<Suspense fallback={<Skeleton />}>
<SlowDataComponent /> {/* Streams in when ready */}
</Suspense>
</div>
);
}
This is more nuanced than traditional SSR. With SSR, the entire page waits for all data. With RSC streaming, parts of the page can appear instantly while slower parts stream in.
When to Choose RSC vs SSR vs CSR
Use RSC (in Next.js App Router) when: you are building a new Next.js project, the component needs data from a database or internal service, and the component has no interactivity.
Use traditional SSR (getServerSideProps in Pages Router, or equivalent) when: you are on Pages Router, need full-page server rendering, or are working in a framework that does not support RSC.
Use CSR (client-side rendering with useEffect or SWR) when: data depends on user state that only exists in the browser, data changes frequently after mount, or you need optimistic updates.
Most real applications use all three. The skill is knowing which to reach for in each situation.
Keep Reading
- Next.js App Router Patterns in 2026: What to Use and What to Avoid -- practical App Router patterns that build on RSC
- Next.js Caching in 2026: The Complete Guide to All Four Layers -- how RSC output gets cached
- React State Management in 2026: Which Approach Actually Makes Sense -- where server state and client state meet
Pristren builds AI-powered software for teams. Zlyqor is our all-in-one workspace -- chat, projects, time tracking, AI meeting summaries, and invoicing -- in one tool. Try it free.