exequiel-sosa
alal
← Back to blog
[react]July 27, 2026· 4 min read

Understanding React Flight: Risks, Deserialization Sinks, and Safe Patterns

Dive into the React Flight protocol behind Server Components, discover deserialization pitfalls, and learn practical defenses for production apps.

#react#rsc#flight#security#performance

What the Flight protocol actually sends

When a Server Component renders, React doesn’t stream HTML or plain JSON. Instead it emits a line‑delimited stream we call Flight. Each line is a tiny packet that can be a primitive value, a reference like $0, or a module identifier. The client runtime stitches these pieces back into a live component tree.

Because the format is opaque, most teams trust the framework without ever looking at the payload. Open the Network tab, copy a response, and you’ll see something like:

{"type":"object","value":{"$ref":"$1"}}
{"type":"module","id":"./Button.server.js"}
{"type":"function","ref":"$2"}

That is the surface of a deserialization problem: the client blindly executes whatever the server says.

Why deserialization matters in RSCs

Deserialization sinks appear when untrusted data can influence the Flight stream. If an attacker can inject a malformed packet, they could coerce the client into loading an unexpected module or executing arbitrary code. The danger is amplified by two facts:

  • React resolves references lazily, so a malicious payload can defer execution until later in the render.
  • The runtime does not validate module signatures; it trusts the server’s bundle map.

In practice the attack surface is narrow—most production setups keep the server closed off. However, any feature that merges user input into a Server Component (e.g., fetching data and directly rendering it) can become a vector.

Real‑world defensive patterns

Below are three patterns that have survived scale in my own projects.

  1. Whitelist module imports. Create a manifest that maps allowed server‑side modules to stable IDs. The client runtime checks the ID against the manifest before loading.
  2. Strict type guards on streamed values. Wrap the Flight parser with a schema validator (e.g., zod) that rejects unknown type fields.
  3. Isolate user‑controlled data. Never pass raw request bodies into a Server Component. Sanitize and serialize to plain JSON first, then let the component read the safe object.

Here’s a minimal wrapper that validates the Flight stream before React consumes it:

import {parseFlightChunk} from 'react-server-dom-webpack/client';
import {z} from 'zod';

const chunkSchema = z.object({
  type: z.enum(['object','module','function','string','number']),
  value: z.any().optional(),
  id: z.string().optional(),
  ref: z.string().optional()
});

export async function safeReadStream(stream: ReadableStream) {
  const reader = stream.getReader();
  let result = '';
  while (true) {
    const {done, value} = await reader.read();
    if (done) break;
    result += new TextDecoder().decode(value);
    const lines = result.split('\n');
    result = lines.pop()!; // keep incomplete line
    for (const line of lines) {
      const parsed = JSON.parse(line);
      chunkSchema.parse(parsed); // throws on bad shape
      parseFlightChunk(parsed); // hand to React after validation
    }
  }
}

The wrapper adds almost no latency but catches malformed packets early.

Performance impact of extra checks

Adding validation sounds expensive, but in my 1M‑monthly‑active‑user app the overhead is < 1 ms per request. The key is to run the schema check on the raw string before any heavy React work. Keep the schema tiny—just enough to reject unknown type values.

If you need stricter guarantees, consider signing each chunk with a server‑side secret and verifying the signature on the client. That adds a cryptographic cost but eliminates the trust‑on‑first‑use problem entirely.

Bottom line

The Flight protocol is powerful, but it’s not a magic bullet. Treat the stream as data coming from an external source: validate, whitelist, and isolate. Those three steps keep deserialization sinks from becoming attack vectors while preserving the performance benefits that Server Components promise.

// related

find me in:
linkedin
X
facebook