facebook/react · error · Error

React currently only supports piping to one writable stream.

Error message

React currently only supports piping to one writable stream.

What it means

react-server-dom-parcel's renderToPipeableStream returns a pipeable handle guarded by a hasStartedFlowing flag. Once pipe(destination) is called, the Flight request starts flushing rows to that single Node Writable. React throws on any second pipe() call because interleaving the same request's rows into two streams would corrupt the RSC protocol stream.

Source

Thrown at packages/react-server-dom-parcel/src/server/ReactFlightDOMServerNode.js:217

    options ? options.identifierPrefix : undefined,
    options ? options.temporaryReferences : undefined,
    options ? options.startTime : undefined,
    __DEV__ && options ? options.environmentName : undefined,
    __DEV__ && options ? options.filterStackFrame : undefined,
    debugChannelReadable !== undefined,
  );
  let hasStartedFlowing = false;
  startWork(request);
  if (debugChannelWritable !== undefined) {
    startFlowingDebug(request, debugChannelWritable);
  }
  if (debugChannelReadable !== undefined) {
    startReadingFromDebugChannelReadable(request, debugChannelReadable);
  }
  return {
    pipe<T: Writable>(destination: T): T {
      if (hasStartedFlowing) {
        throw new Error(
          'React currently only supports piping to one writable stream.',
        );
      }
      hasStartedFlowing = true;
      startFlowing(request, destination);
      destination.on('drain', createDrainHandler(destination, request));
      destination.on(
        'error',
        createCancelHandler(
          request,
          'The destination stream errored while writing data.',
        ),
      );
      // We don't close until the debug channel closes.
      if (!__DEV__ || debugChannelReadable === undefined) {
        destination.on(
          'close',
          createCancelHandler(request, 'The destination stream closed early.'),

View on GitHub (pinned to eafeac097b)

Solutions

  1. Call renderToPipeableStream() again to create a new request for each additional destination instead of reusing one handle
  2. If you need the same output in two places, pipe once into a stream.PassThrough and fan out from that
  3. When you must replay the payload (caching), buffer the render first (renderToBuffer/string) and then write it to each destination

Example fix

// before
const {pipe} = renderToPipeableStream(<App />, options);
pipe(res);
pipe(cacheStream); // Error: only one writable stream

// after
const {pipe} = renderToPipeableStream(<App />, options);
pipe(res);
const {pipe: pipeToCache} = renderToPipeableStream(<App />, options);
pipeToCache(cacheStream); // separate render per destination
Defensive patterns

Strategy: validation

Validate before calling

const {pipe} = renderToPipeableStream(<App />, options);
let piped = false;
function pipeOnce(destination) {
  if (piped) {
    throw new Error('Stream already piped; create a new render instead.');
  }
  piped = true;
  return pipe(destination);
}

Prevention

When it happens

Trigger: Calling pipe() twice on the same handle returned by renderToPipeableStream: piping once to the http.ServerResponse and again to a cache write-stream, or re-piping to a fresh response after a client disconnect/abort retry.

Common situations: SSR error handlers that swap destinations mid-stream; middleware wanting to tee the RSC payload for logging or edge caching; retry loops that call pipe(res) again after backpressure or a socket error.

Related errors


AI-assisted analysis of facebook/react@eafeac097b (2026-08-21). Data as JSON: /api/errors/e2998ba98eb47865. Report an issue: GitHub.