Back to TechSheets
ReactNext.jsWebPerformanceFrontendArchitecture

Demystifying the React Server Components Wire Format: A Deep-Dive into RSC Payloads

Thanga MariappanSenior Architect
6 min read
Jun 8, 2026
Demystifying the React Server Components Wire Format: A Deep-Dive into RSC Payloads

Demystifying the React Server Components Wire Format: A Deep-Dive into RSC Payloads

Have you ever opened the network tab while inspecting a modern Next.js or React Server Components (RSC) application and stumbled upon a stream of cryptic, single-letter-prefixed lines? If you have wondered what actually travels across the network when a server component renders, you are in the right place.

It is not raw HTML, nor is it standard JSON. It is a highly optimized, streamed serialization format often referred to as the Flight Protocol payload. In this deep-dive, we will pull back the curtain on this wire format, understand how React serializes complex component trees, and explore how this knowledge can make you a better debugger and system architect.

The Core Shift: Beyond Traditional Hydration

In traditional Single Page Applications (SPAs), the server sends a minimal HTML file, and the client downloads a large JavaScript bundle to build the Virtual DOM from scratch. In classic Server-Side Rendering (SSR), the server renders HTML on the initial request, but the client must still download the same amount of JavaScript to "hydrate" the page and make it interactive.

React Server Components introduce a third paradigm. They execute exclusively on the server. The result of this execution is not converted directly to HTML on the fly for subsequent client-side navigations; instead, it is serialized into a special streamable JSON-like format.

This format allows React to:

  1. Stream rendering results chunk-by-chunk over a single HTTP connection.
  2. Refetch parts of the UI without losing client state (such as text input focus, active animations, or scroll position).
  3. Keep massive server-only dependencies out of the client bundle.

Deconstructing the RSC Wire Format

Let us look at a real-world example. Imagine we have a simple server component that imports and renders a client component:

// src/ClientComponent.js (Client Component)
"use client";
export default function ClientComponent({ message }) {
  return <button onClick={() => alert(message)}>Click me</button>;
}

// src/Page.js (Server Component)
import ClientComponent from './ClientComponent';

export default function Page() {
  return (
    <main>
      <h1>Welcome to the Deep Dive</h1>
      <ClientComponent message="Hello from the Server!" />
    </main>
  );
}

When this page renders, the server streams the following payload to the browser. Let us break down what each line means:

M1:{"id":"./src/ClientComponent.js","chunks":["client-chunk.js"],"name":"default"}
J0:["$","main",null,{"children":[["$","h1",null,{"children":"Welcome to the Deep Dive"}],["$","$L1",null,{"message":"Hello from the Server!"}]]}]

1. The Module Reference (M-Prefix)

The line starting with M1 does not contain UI elements. Instead, it is a reference to a Client Component.

  • M1: Registers a Client Component reference with the unique ID of 1.
  • id: Points to the relative file path of the module.
  • chunks: Specifies which JS bundles the browser needs to download dynamically to execute this component.

2. The Element Tree (J-Prefix)

The line starting with J0 represents the actual React Element tree structure.

  • J0: Represents the root React render tree.
  • ["$","main",...]: The $ symbol signifies a React Element. Here, it describes a native HTML main tag.
  • ["$","$L1",...]: Notice the $L1 inside the children array. The $L denotes a "Lazy" reference, pointing directly back to the client module defined in M1. This is how React stitches Server-rendered layout structure together with Client-rendered interactive components.

How the Flight Protocol Handles Promises and Streaming

One of the most powerful features of the RSC architecture is how it handles asynchronous data fetching. If a Server Component performs an asynchronous operation, the server does not block the entire page load. It streams the initial layout immediately and pushes the resolved data down the same connection when it is ready.

Consider this async component:

async function DelayedData() {
  const data = await fetchSomeData();
  return <p>{data.message}</p>;
}

During the initial stream, React places a placeholder (like a Suspense boundary fallback) in the root tree. Once the promise resolves, React pushes a new line to the stream:

S2:"$h"
J2:["$","p",null,{"children":"Resolved data message"}]

Here:

  • S2: Declares a suspended boundary transition slot.
  • J2: Contains the resolved React elements that will dynamically replace the Suspense placeholder in the browser DOM, without requiring a full page reload or losing any client-side state.

Key Takeaways

  • RSC is not HTML: The RSC payload is an intermediate, streamable representation of the React virtual DOM tree, designed for fast client-side integration.
  • Client Components are References: Client components are not compiled into the server-rendered stream payload; instead, they are referenced by metadata rows (M-prefix) which tells the client which JS files to fetch asynchronously.
  • Suspense is Native: The Flight Protocol handles asynchronous resolution natively by sending replacement UI fragments as soon as server promises resolve.
  • Zero-bundle Size Impact: Code executed exclusively on the server stays on the server, drastically reducing the initial JavaScript footprint of your application.

How You Can Use This

  1. Optimizing Network Waterfalls: When debugging performance, open your browser's Network Tab, filter by Fetch/XHR, and look for stream responses. If you see huge gaps in time between streamed chunks, you have a slow-resolving Promise blocking your server stream. Wrap that component in Suspense to unblock the rest of the stream immediately.
  2. Hydration Match Debugging: If you ever experience hydration mismatch errors, inspect the RSC payload to see exactly what props the server sent to the client. This helps isolate whether the mismatch occurred because of client-side local state or timezone discrepancies.
  3. Auditing Bundle Sizes: Check the M-prefix rows to ensure you are not accidentally bringing large third-party libraries into your client components. If a library is only used to compute static UI elements, move that computation back to a Server Component.

Internal Linking Suggestions

  • Unlocking Next.js Performance with Dynamic Suspense Boundaries (Focus on utilizing the streaming protocol)
  • The Complete Guide to Hydration in Modern JavaScript Frameworks (Provides baseline context for SSR vs RSC)

Social Media Sharing Captions

LinkedIn

🚀 Ever opened your network tab on a React Server Components app and wondered what those mysterious "M1" and "J0" lines are?

It is not HTML, and it is not standard JSON. It is the Flight Protocol payload—a highly optimized, streamable representation of your UI.

In my latest deep-dive article, I break down exactly how React serializes Server and Client components, how it streams promises over a single connection, and how you can use this knowledge to debug hydration mismatches and performance bottlenecks.

Read the full breakdown here! 👇

#react #nextjs #webdevelopment #frontend #systemdesign #javascript

Medium

Title: Demystifying the React Server Components Wire Format

Have you ever wondered how React Server Components actually communicate with the browser? Learn about the inner workings of the Flight Protocol, how React streams component updates, and how to read the RSC payload in your browser network tab. Read on for a complete architectural guide!

0Likes
Share this techsheet
Share TechSheet

Discussion

0 characters