React Server Components 01 🧬 What RSC Actually Is — A component type, not a framework
React Server Components (RSC) are a new type of component that renders ahead of time, before bundling, in an environment separate from your client app or SSR server . The “server” in RSC refers to this separate environment—it can run once at build time on a CI server, or per-request on a web server . The previous chapters covered the state management and the data fetching patterns; this chapter establishes what RSC is at its core, and why the distinction between “a component type” and “a framework” matters.
The most common misunderstanding is that RSC is a framework feature. It is not. RSC is a React component type, and it requires a bundler or framework to implement the server/client boundary, but the component model itself is part of React . Next.js App Router is the most prominent implementation, but the concept is not tied to Next.js. The React documentation states explicitly: “Server Components are a new type of Component” . The framework provides the infrastructure; the component type is React’s.
Key point: There is no directive for Server Components. The 'use client' directive marks the Client Components, but the absence of the directive means the component is a Server Component . The 'use server' directive is for Server Functions, not for Server Components . This asymmetry is the source of much confusion.
Why RSC is a component type
The pre-bundling render problem.
A Server Component renders before the bundling step. This means its code is not included in the JavaScript bundle sent to the browser . The component can read the filesystem, query the database, or fetch static content without the client ever downloading the code that performs those operations . A Client Component, by contrast, ships its code to the browser, where React hydrates it to make it interactive .
The async component problem.
Server Components introduce async components: a component can be an async function and await in the render . This is not supported in Client Components because the browser cannot suspend the render on a promise in the same way . The async component is a feature of the Server Component type, not a framework addition.
The serialization problem.
Server Components pass data to Client Components as props. The props must be serializable by React . The RSC Payload is the mechanism that carries the rendered output and the serialized props . The payload contains the rendered result of the Server Components, placeholders for the Client Components, and the serialized props . The framework handles the transport; the component type defines what is sent.
a. The component type distinction
A Server Component is a function that renders on the server. It can be async, it can access the backend, and it does not ship to the browser.
// A Server Component (no directive needed)
async function PostsPage() {
const posts = await db.query('SELECT * FROM posts')
return (
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}
The PostsPage is an async function, and it awaits the database query during the render . The component’s code never reaches the browser. Only the rendered output—the list of titles—is serialized into the RSC Payload .
A Client Component is a function marked with the 'use client' directive. It ships to the browser and can use interactivity.
'use client'
import { useState } from 'react'
function LikeButton({ likes }: { likes: number }) {
const [count, setCount] = useState(likes)
return <button onClick={() => setCount(count + 1)}>❤️ {count}</button>
}
The 'use client' directive tells the bundler that the LikeButton and its imports belong to the client module graph . The useState and the onClick are client features that require the directive.
b. The module graph boundary
The boundary between Server and Client Components is a boundary in the module graph, not the render tree . Once a file is marked with 'use client', its imports and the components it directly renders become Client Components. A Server Component passed as a children prop, however, is not imported into the client graph; it is rendered on the server and passed as the already-rendered output .
// Client Component: Modal.tsx
'use client'
import { useState } from 'react'
function Modal({ children }: { children: React.ReactNode }) {
const [open, setOpen] = useState(false)
return (
<div>
<button onClick={() => setOpen(!open)}>Toggle</button>
{open && <div>{children}</div>}
</div>
)
}
// Server Component: page.tsx
import Modal from './Modal'
import Cart from './Cart' // Cart is a Server Component
function Page() {
return (
<Modal>
<Cart /> // Rendered on the server, passed as children
</Modal>
)
}
The Cart is a Server Component, and it is passed as the children of the Modal . The Cart is rendered on the server, and its output is included in the RSC Payload. The Modal receives the rendered output, not the Cart module, so the Cart code never reaches the client .
c. The framework dependency
RSC requires a bundler or framework that implements the server/client boundary . The React documentation states: “Server Components are a framework feature. They require a framework like Next.js (App Router), or a custom setup with a bundler that supports RSC. They cannot be used in standalone React applications” .
The framework provides the RSC Payload transport, the module graph splitting, and the server rendering infrastructure. The component type is React’s; the implementation is the framework’s. This is why RSC is not a framework—it is a component type that a framework can implement.
Complete Example Session
// ============================================
// PART 1: THE SERVER COMPONENT
// ============================================
async function ProductPage({ id }: { id: string }) {
const product = await db.products.find(id)
return (
<div>
<h1>{product.name}</h1>
<AddToCartButton productId={id} />
</div>
)
}
// ============================================
// PART 2: THE CLIENT COMPONENT
// ============================================
'use client'
function AddToCartButton({ productId }: { productId: string }) {
const [added, setAdded] = useState(false)
return (
<button onClick={() => { setAdded(true); addToCart(productId) }}>
{added ? 'Added' : 'Add to Cart'}
</button>
)
}
// ============================================
// PART 3: THE RSC PAYLOAD CONCEPT
// ============================================
// The payload contains:
// - The rendered output of ProductPage (the h1)
// - A reference to the AddToCartButton client module
// - The serialized productId prop
// ============================================
// PART 4: THE COMPOSITION
// ============================================
// The ProductPage is a Server Component.
// The AddToCartButton is a Client Component.
// The ProductPage renders the AddToCartButton with the productId prop.
// The productId is serializable, so it crosses the boundary.
// ============================================
// PART 5: THE MODULE GRAPH
// ============================================
// Server module graph: ProductPage, db
// Client module graph: AddToCartButton, useState
// The two graphs are separate; only the serialized props cross.
// ============================================
// PART 6: THE DATA ACCESS
// ============================================
// The db query runs on the server, inside the ProductPage render.
// The db module never reaches the browser.
// The client receives only the rendered output and the prop.
The six parts demonstrate the RSC model: the Server Component with the data access, the Client Component with the interactivity, the RSC Payload concept, the composition, the module graph split, and the data access boundary.
Quick Reference
Server vs Client
| Feature | Server Component | Client Component |
|---|---|---|
| Directive | None (default) | 'use client' |
| Runs on server | Yes | Yes (SSR) |
| Runs in browser | No | Yes (hydration) |
| Async/await | Yes | No |
useState/useEffect | No | Yes |
| Event handlers | No | Yes |
| Direct DB/secrets | Yes | No |
| Bundle size | Zero | Included |
Directives
| Directive | Purpose |
|---|---|
'use client' | Mark a Client Component boundary |
'use server' | Mark Server Functions (not Server Components) |
The RSC Payload
| Content | Purpose |
|---|---|
| Rendered Server Component output | The HTML-like result |
| Client Component placeholders | Where the client components render |
| Serialized props | The data passed across the boundary |
Serialization Rules
| Prop Type | Serializable |
|---|---|
| String, number, boolean | ✅ |
| Array, plain object | ✅ |
| React element | ✅ |
| Function | ❌ (use Server Function) |
| Class instance | ❌ |
Best Practices
✅ Do This:
// Start with the Server Components
async function Page() { ... } # ✅
// Add the 'use client' only where the interactivity is needed
'use client' # ✅
// Pass the Server Components as the children
<ClientWrapper><ServerComponent /></ClientWrapper> # ✅
// Keep the client leaves small
<AddToCartButton /> # ✅
// Pass the serializable data only
<Client likes={post.likes} /> # ✅
❌ Don’t Do This:
// Add the 'use client' to the static components
'use client' // unnecessary # ❌
// Use the 'use server' for the Server Components
'use server' // This is for Server Functions # ❌
// Import the Server Component into the Client Component
import { ServerComp } from './server' // Becomes client # ❌
// Pass the functions as the props
<Client onClick={() => {}} /> // Not serializable # ❌
// Use the context in the Server Component
useContext(ThemeContext) // Not supported # ❌
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| The whole page is client-side | 'use client' at the top | Move it to the interactive leaf |
| The Server Component becomes client | Imported into a Client Component | Pass it as children |
| The serialization error | A function prop | Use a Server Function or remove it |
| The context is undefined | Used in a Server Component | Create a Client provider |
The 'use server' misuse | Confused with the Server Component directive | Use it only for Server Functions |
Real-World Examples
1. Server Component with data
async function Page() {
const data = await db.query(...)
return <List data={data} />
}
2. Client Component with state
'use client'
function Counter() {
const [count, setCount] = useState(0)
return <button onClick={() => setCount(count + 1)}>{count}</button>
}
3. Client wraps Server children
<Modal><Cart /></Modal>
4. Server passes Client as child
<Page><Counter /></Page>
5. The 'use client' at the leaf
// AddToCartButton.tsx
'use client'
export function AddToCartButton() { ... }
6. The RSC Payload content
The payload contains:
- The rendered Server Component output
- The Client Component placeholders
- The serialized props
7. The module graph split
Server graph: Page, db
Client graph: Counter, useState
The two graphs are separate.
8. The serialization check
<Client data={{ id: 1, name: 'Test' }} /> // ✅ plain object
<Client onClick={() => {}} /> // ❌ function
9. The async component
async function Page() {
const data = await fetch(...)
return <div>{data}</div>
}
10. The context provider
'use client'
function ThemeProvider({ children }) {
return <ThemeContext.Provider value="dark">{children}</ThemeContext.Provider>
}
Visual
The Component Type Split
┌──────────────────────────────────────────────────────────────┐
│ RSC IS A COMPONENT TYPE, NOT A FRAMEWORK │
│ │
│ React core: │
│ ├── Server Component type (async, server-only) │
│ └── Client Component type ('use client') │
│ │
│ Framework (Next.js, etc.): │
│ ├── The RSC Payload transport │
│ ├── The module graph splitting │
│ └── The server rendering infrastructure │
│ │
│ The framework implements the type; it does not define it. │
└──────────────────────────────────────────────────────────────┘
The Module Graph Boundary
┌──────────────────────────────────────────────────────────────┐
│ THE BOUNDARY IS IN THE MODULE GRAPH │
│ │
│ Server module graph: │
│ ├── app/page.tsx (Server) │
│ ├── app/ui/cart.tsx (Server) │
│ └── app/ui/modal.tsx (Client) ← the boundary │
│ │
│ Client module graph: │
│ ├── app/ui/modal.tsx (Client) │
│ └── (its imports) │
│ │
│ The Cart is passed as children to the Modal. │
│ The Cart module is not in the client graph. │
└──────────────────────────────────────────────────────────────┘
The RSC Payload
┌──────────────────────────────────────────────────────────────┐
│ THE PAYLOAD CONTAINS THE RENDERED OUTPUT │
│ │
│ On the server: │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ Server Component tree → RSC Payload │ │
│ │ ├── the rendered result of the Server Components │ │
│ │ ├── the placeholders for the Client Components │ │
│ │ └── the serialized props for the Client Components │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
│ On the client: │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ HTML (the initial preview) │ │
│ │ + RSC Payload (reconcile the trees) │ │
│ │ + JS (hydrate the Client Components) │ │
│ └────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────┘
Summary
| Item | Value |
|---|---|
| RSC | React Server Components |
| Nature | A component type, not a framework |
| Directive | None (absence of 'use client') |
'use server' | For Server Functions, not Server Components |
| Rendering | Before bundling, on the server |
| Async | Supported in Server Components |
| Client boundary | 'use client' directive |
| Payload | RSC Payload with rendered output and props |
| Framework | Required for the implementation |
| Next.js | The most prominent implementation |
Key takeaways:
- RSC is a component type, not a framework. The Server Components are a new type of React component that renders ahead of time, before bundling, in a separate environment . The framework provides the infrastructure; the component type is React’s.
- There is no directive for Server Components. The
'use client'directive marks the Client Components, and the absence of the directive means the component is a Server Component . The'use server'directive is for Server Functions, not for Server Components . - The boundary is in the module graph. Once a file is marked with
'use client', its imports and the components it directly renders become Client Components. A Server Component passed as achildrenprop is not imported into the client graph . - Server Components can be async. They can
awaitin the render, which is not supported in Client Components . The async component is a feature of the Server Component type. - The RSC Payload is the transport. It contains the rendered output of the Server Components, the placeholders for the Client Components, and the serialized props . The framework handles the transport.
- RSC requires a framework. It cannot be used in standalone React applications. The framework implements the server/client boundary, the payload transport, and the module graph splitting .
Remember: React Server Components are a component type. The Server Component is a function that renders on the server, can be async, and does not ship to the browser. The Client Component is a function marked with 'use client' that ships to the browser and handles the interactivity. The framework is the implementation, not the definition. The RSC Payload is the transport. The result is a component model where the server handles the data and the static rendering, and the client handles the interactivity, without the server code ever reaching the browser.
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!