Skip to content

All notes

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
  • RSC
  • Server Components
  • Next.js
  • Rendering
  • Performance
server, payload, browser

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
       ↓
Browser

In modern React architecture, the story is more involved.

We need to understand this path:

React Tree
    ↓
Server Components
    ↓
RSC Payload
    ↓
Client Components
    ↓
Browser
    ↓
Hydration / Interaction

That 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              │
│                      │                      │
└──────────────────────┴──────────────────────┘
The server/client boundary is the key mental model of RSC.

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
        ↓
      Browser

That 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 metadata
RSC Payload is not simply "HTML sent to the browser".

This distinction matters.

If you only think:

Server Component → HTML

you 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
        ↓
UI

04What happens to `AddToCartButton`?

Back to:

<AddToCartButton productId={product.id} />

This component has:

"use client";

That creates a boundary.

Server Component
       │
       │
       ▼
"use client"
       │
       ▼
Client Component

The 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 APIs
`"use client"` defines a client boundary, not simply "render this component in the browser".

That 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
    ↓
Browser

Instead of:

Browser
   ↓
fetch()
   ↓
API
   ↓
Database
   ↓
API
   ↓
Browser

That 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 UI

08The complete pipeline

The complete path from a request to interactive UI.

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
       ↓
Browser

try 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      Component

Do 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
                        │
                        ▼
                   Interaction
The real power of RSC is not "rendering on the server". It is controlling where your application logic executes.

Conclusion

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
  ↓
Interaction

Once 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.