React / Deep Dive
React Server Components: What Actually Happens Under the Hood?
A visual deep dive into Server Components, Client Components, RSC Payload, hydration, and the server/client boundary.
15 Apr 2026 · 15–20 min read
React Server Components are often introduced with a very simple sentence:
"Server Components run on the server."
That sentence is true, but it is not enough.
If that is all you remember, it is easy to form the wrong mental model:
Server Component
↓
HTML
↓
BrowserIn modern React architecture, the story is more involved.
We need to understand this path:
React Tree
↓
Server Components
↓
RSC Payload
↓
Client Components
↓
Browser
↓
Hydration / InteractionThat is what this note will take apart.
01Start with one simple page
Suppose we have:
export default async function ProductPage() {
const product = await getProduct();
return (
<main>
<h1>{product.name}</h1>
<AddToCartButton productId={product.id} />
</main>
);
}and:
"use client";
export function AddToCartButton({
productId,
}: {
productId: string;
}) {
const [loading, setLoading] = useState(false);
return (
<button>
Add to cart
</button>
);
}We now have two worlds:
SERVER CLIENT
ProductPage AddToCartButton
│ │
│ │
└────────── Network ───────────────┘┌──────────────────────┬──────────────────────┐
│ SERVER │ BROWSER │
│ │ │
│ ProductPage │ AddToCartButton │
│ getProduct() │ useState() │
│ database/API │ onClick │
│ │ │
└──────────────────────┴──────────────────────┘02A Server Component is not an "HTML component"
A common misconception:
"A Server Component only produces HTML."
That is the wrong picture.
React Server Components have their own representation, sent from the server to the client.
You can think of it like this:
React Server Tree
↓
RSC Renderer
↓
RSC Payload
↓
BrowserThat payload holds what React needs to reconstruct the tree and combine it with Client Components.
03The RSC Payload
Server
────────────────────────────────
ProductPage
│
├── Header
│
├── Product
│
└── AddToCartButton
│
▼
Client Component
│
│ reference
▼
RSC Payload
│
│
──────────────┼────────────────
│ Network
──────────────┼────────────────
▼
Browser
RSC Payload
- rendered Server Component tree
- references to Client Components
- serialized props
- framework metadataThis distinction matters.
If you only think:
Server Component → HTMLyou will not understand why React can keep working with the component tree on the client.
A better mental model is:
Server Component
↓
React Server Renderer
↓
RSC Payload
↓
Client React
↓
UI04What happens to `AddToCartButton`?
Back to:
<AddToCartButton productId={product.id} />This component has:
"use client";That creates a boundary.
Server Component
│
│
▼
"use client"
│
▼
Client ComponentThe server can render a tree that contains a reference to a Client Component.
But the code the Client Component needs must exist in the browser if the component is going to be interactive.
05Why `"use client"` matters
SERVER
────────────────────────────────
ProductPage
ProductData
Database
Server APIs
│
│ "use client"
▼
────────────────────────────────
CLIENT
AddToCartButton
useState
onClick
Browser APIsThat is why "use client" should not be added casually.
For example:
"use client";
export function ProductTitle() {
return <h1>MacBook Pro</h1>;
}This component does not need:
- state
- an effect
- a browser API
- an event handler
If you place it inside a Client Component boundary, you are sending extra code to the client for no clear reason.
06The data flow
Suppose:
const product = await getProduct();runs on the server.
The flow can look like this:
Database
↓
Server
↓
getProduct()
↓
ProductPage
↓
RSC Payload
↓
BrowserInstead of:
Browser
↓
fetch()
↓
API
↓
Database
↓
API
↓
BrowserThat can help avoid unnecessary client-server waterfalls in some application architectures. Next.js also describes Server Components as part of an approach that moves data fetching onto the server and reduces extra round trips.
07But where does hydration happen?
This is where many explanations of RSC become vague.
Keep two ideas separate:
Server Components
They do not need hydration in the browser the way Client Components do.
Client Components
They need client-side JavaScript to become interactive.
For example:
"use client";
function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
{count}
</button>
);
}The browser needs JavaScript for:
click
↓
event handler
↓
setState()
↓
render
↓
update UI08The complete pipeline
09Why this architecture can improve performance
Three things are worth keeping distinct:
1. Less client JavaScript
Server-only logic does not have to become browser JavaScript.
2. Server-side data access
Data can be fetched closer to server-side resources.
3. Streaming
UI can be sent and rendered in pieces in architectures that support it.
But:
Server Components are not automatically faster.
An application can still be slow if:
- the database query is slow
- the server response is slow
- the component tree is too large
- network latency is high
- the client bundle is large
- hydration is heavy
- caching is a poor fit
10The most important rule: minimize the client boundary
A useful mental model:
Server
┌──────────────────┐
│ │
│ Server UI │
│ │
│ Data fetching │
│ │
│ Business logic │
│ │
└────────┬─────────┘
│
small client boundary
│
▼
┌──────────────────┐
│ Interactive UI │
└──────────────────┘Instead of:
"use client"
Whole application
↓
Browsertry to keep the boundary smaller when the architecture allows it.
11A practical example
A bad boundary:
"use client";
export default function ProductPage() {
const [quantity, setQuantity] = useState(1);
return (
<ProductPageLayout>
<ProductInformation />
<ProductDescription />
<ProductReviews />
<ProductImages />
<QuantitySelector />
</ProductPageLayout>
);
}One large boundary has turned the whole subtree into client-side territory.
Another approach:
export default async function ProductPage() {
const product = await getProduct();
return (
<ProductPageLayout>
<ProductInformation product={product} />
<ProductDescription
description={product.description}
/>
<ProductReviews
productId={product.id}
/>
<QuantitySelector
productId={product.id}
/>
</ProductPageLayout>
);
}and:
"use client";
function QuantitySelector({
productId,
}: {
productId: string;
}) {
const [quantity, setQuantity] = useState(1);
// ...
}The boundary is clearer.
12Server vs Client: use this mental model
SERVER
│
┌──────┴──────┐
│ │
Data/UI Interactive
│ │
▼ ▼
Server Client
Component ComponentDo not turn this table into an absolute law.
Treat it as a mental model.
A better question than:
"Can this component be a Server Component?"
is:
"Where does this component actually need to execute?"
13The debugging mindset
When a component misbehaves, ask:
Question 1
Is this component on the server or the client?
Question 2
Where is the boundary?
Question 3
Where is the data fetched?
Question 4
How much JavaScript actually needs to be sent to the browser?
Question 5
Does this component need browser interaction?
Question 6
Are we creating a client boundary that is too large?
Those questions are more useful than only asking:
"Why isn't my component rendering?"
14The final mental model
REACT TREE
│
┌──────────┴──────────┐
│ │
SERVER CLIENT
│ │
Server Components Client Components
│ │
└──────────┬──────────┘
│
RSC Payload
│
Network
│
▼
Browser
│
Hydration
│
▼
InteractionConclusion
React Server Components are not only a rendering technique.
They change how we divide a frontend application.
The simplest mental model is:
Server
↓
Server Component Tree
↓
RSC Payload
↓
Client Boundary
↓
Browser
↓
InteractionOnce that network boundary is clear, ideas such as:
"use client"- hydration
- RSC Payload
- Server Actions
- streaming
- caching
- the client bundle
start to fit together as one system.
And instead of asking:
"Should I use Server Components?"
you can ask a more technical question:
"Which part of my application actually needs to run in the browser?"
That is the mental model that matters.
Further reading
- React Server Components
- Next.js App Router
- Next.js Rendering
- Next.js Caching
- React Server Components Architecture
15 Apr 2026
That’s all for this note.