facebook/react · error · Error

417

417

Error message

React currently only supports piping to one writable stream.

What it means

Thrown by the react-flight-server-fb (Flight / Server Components) node runtime: the object returned by its render-to-pipeable-stream API keeps a hasStartedFlowing closure flag, and a second pipe(destination) call throws because one Flight request can stream its output to only one writable. The request's flow state and buffered chunks are single-use, so re-piping is not a supported operation.

Source

Thrown at packages/react-flight-server-fb/src/server/ReactFlightDOMServerNode.js:223

    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 pipe() exactly once per request; render a new request with renderToPipeableStream(...) for each destination.
  2. If several sinks need the same payload, pipe once into a Node stream.PassThrough and tee the bytes from there.
  3. After a destination error, create a fresh render (abort the old request, re-render) instead of re-piping the same object.

Example fix

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

// after
const {pipe} = renderToPipeableStream(<App />, options);
const passThrough = new stream.PassThrough();
pipe(passThrough);
passThrough.pipe(res);
passThrough.pipe(tee);
Defensive patterns

Strategy: validation

Validate before calling

let piped = false;
function pipeOnce(request, destination) {
  if (piped) {
    throw new Error('This stream was already piped; render a new request.');
  }
  piped = true;
  return request.pipe(destination);
}

Try / catch

try {
  request.pipe(destination);
} catch (e) {
  if (/one writable stream/.test(e.message)) {
    const fresh = renderToPipeableStream(<App />, options);
    fresh.pipe(destination);
  } else {
    throw e;
  }
}

Prevention

When it happens

Trigger: Calling pipe(destination) twice on the same request object returned by the FB flight server's renderToPipeableStream-style API - for example piping to an HTTP response and then to a second sink (log file, cache), or re-piping to a fresh destination after the first one errored.

Common situations: Writing one render's payload to multiple sinks; retry logic that calls pipe() again after a socket error; middleware (compression, logging) that transparently pipes the same stream a second time.

Related errors


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