DEV Community

Cover image for React Server Components: Under the Hood of Streaming and Client Boundaries
Srikar Phani Kumar Marti
Srikar Phani Kumar Marti

Posted on • Originally published at blog.mspk.me

React Server Components: Under the Hood of Streaming and Client Boundaries

You’ve probably hit that moment where your React app feels sluggish, or where splitting code between client and server logic gets messy fast. React Server Components (RSC) promise to fix that by letting you run components on the server while streaming their output to the client , all without losing interactivity. But how does this actually work under the hood?

I dug into the mechanics to understand why the new streaming format feels so fluid and how React knows when to run what.

The familiar pain: mixing server and client logic

Before RSC, if you wanted to fetch data on the server and then render it on the client, you usually did something like data fetching in getServerSideProps, then rendering with client-side hooks or hydration. It worked but had downsides:

  • You send the full HTML only after data arrives, blocking first paint.
  • Client code bundles bloat with data-fetching logic.
  • Hydration can cause flickers or UI mismatches.

React Server Components flip this by letting you write components that run entirely on the server , sending only serialized UI instructions to the client. But it’s not just a magic serialization trick.

Streaming the UI: chunking the response

When you request an RSC-enabled React app, the server doesn’t wait to render the whole tree. Instead, it starts streaming chunks of serialized component data immediately.

Imagine the server is assembling a puzzle but sends you pieces as soon as they’re ready, not the whole picture at once. React serializes each component’s output into a special format , basically a structured JSON that encodes elements, props, and where client boundaries are.

This stream arrives to the browser, where React’s client runtime incrementally parses each chunk and merges it into the current UI.

That means your users start seeing content way faster, because React never waits for the entire tree before flushing something.

Client boundaries: the handshake between server and client

A core concept is the client boundary , where a React Server Component hands off to a client component. Under the hood, React marks these boundaries explicitly in the serialized stream.

Here’s what happens:

  1. Server renders a subtree and encounters a client component.
  2. Instead of serializing that client component’s UI, it inserts a placeholder with a unique ID.
  3. The client runtime sees the placeholder, fetches the necessary client code bundle asynchronously, and then hydrates that component in-place.

This handshake means server and client code stay separate, but React stitches them together seamlessly.

How React serializes components

The serialization format React uses isn’t just plain JSON. It’s a custom encoding that preserves React elements’ shape and references, plus some metadata:

  • Component type identifiers
  • Props, including special handling for functions and client references
  • Boundary markers that tell the client runtime where to hydrate

For example, if a prop is a client function, it gets replaced by a reference ID, so the client can wire up event handlers correctly after hydration.

This encoding is efficient and incremental , React streams each component or subtree as it’s ready, so your app can render progressively.

Streaming versus traditional SSR

You might ask, isn’t server-side rendering streaming already a thing?

Yes, but with traditional SSR, the server streams fully rendered HTML. React Server Components stream serialized React elements instead, which are then rendered on the client. This gives React more control to incrementally hydrate only the parts marked as client boundaries.

It also means your server components never send any client JavaScript or event handlers directly, just the instructions needed to build the UI.

Architectural implications: clean split of concerns

Because server components can’t contain client-only code, your data fetching, secret keys, and heavy logic stay safely on the server.

Meanwhile, client components handle interactivity, hooks, and browser-only APIs. The explicit boundaries mean you can reason about what runs where without messy hacks.

On top of that, streaming lets your app show meaningful UI faster, improving perceived performance.

What I learned debugging a mismatch

I ran into a tricky bug when I mixed a client component inside a server subtree but forgot to mark it properly. React’s stream sent a placeholder, but the client runtime never fetched the corresponding JS bundle.

Result? A blank spot in the UI.

The fix was to ensure I imported client components with a special directive ('use client') so React could identify and serialize the boundary correctly.

This highlighted how crucial those boundaries are , the entire streaming and hydration dance depends on them.

Wrapping up

React Server Components are more than just a new API , they’re a streaming protocol that rethinks how server and client communicate. By serializing component trees incrementally and marking client boundaries explicitly, they let you split logic cleanly and deliver UI faster.

If you’ve felt friction between server and client code in React apps, digging into how RSC streams and hydrates might inspire new ways to architect your projects , and build apps that feel snappier without swallowing your CPU or bandwidth.


Originally published at Under The Hood.

Get the next deep dive in your inbox: subscribe to Under The Hood.

Top comments (0)