facebook/react · error · Error

417

417

Error message

React currently only supports piping to one writable stream.

What it means

renderToPipeableStream returns a PipeableStream whose pipe(destination) attaches the in-flight Fizz request to a writable stream. A request supports exactly one destination, tracked by a local hasStartedFlowing flag; the second pipe() call on the same object throws error 417. Serving another copy requires a whole new renderToPipeableStream request.

Source

Thrown at packages/react-dom/src/server/ReactDOMFizzServerNode.js:144

    options ? options.onAllReady : undefined,
    options ? options.onShellReady : undefined,
    options ? options.onShellError : undefined,
    undefined,
    options ? options.formState : undefined,
  );
}

function renderToPipeableStream(
  children: ReactNodeList,
  options?: Options,
): PipeableStream {
  const request = createRequestImpl(children, options);
  let hasStartedFlowing = false;
  startWork(request);
  return {
    pipe<T: Writable>(destination: T): T {
      if (hasStartedFlowing) {
        throw new Error(
          'React currently only supports piping to one writable stream.',
        );
      }
      hasStartedFlowing = true;
      prepareForStartFlowingIfBeforeAllReady(request);
      startFlowing(request, destination);
      destination.on('drain', createDrainHandler(destination, request));
      destination.on(
        'error',
        createCancelHandler(
          request,
          'The destination stream errored while writing data.',
        ),
      );
      destination.on(
        'close',
        createCancelHandler(request, 'The destination stream closed early.'),
      );

View on GitHub (pinned to eafeac097b)

Solutions

  1. Guard pipe() with your own boolean and call it exactly once per request; create a new renderToPipeableStream request for any retry
  2. To fan out, pipe once into a Node PassThrough/Transform and tee from that stream
  3. Abort the old request (abort()) before creating a fresh one when re-rendering after an error

Example fix

// before
const {pipe} = renderToPipeableStream(<App />, opts);
pipe(res);
// retry path later
pipe(res); // throws

// after
let piped = false;
const stream = renderToPipeableStream(<App />, {
  ...opts,
  onShellReady() {
    if (!piped) { piped = true; stream.pipe(res); }
  },
});
// retries: const retry = renderToPipeableStream(<App />, opts); retry.pipe(res);
Defensive patterns

Strategy: validation

Validate before calling

let hasPiped = false;
const stream = renderToPipeableStream(<App />, {
  onShellReady() {
    if (hasPiped) return; // guard against double pipe
    hasPiped = true;
    stream.pipe(res);
  },
});

Prevention

When it happens

Trigger: Calling result.pipe(res) twice on the object returned by renderToPipeableStream — once in the main path and again in a retry/timeout/error handler; teeing output by calling pipe on both the HTTP response and a cache stream.

Common situations: Express/Fastify handlers that pipe on onShellReady and again in onError recovery; cache middleware that tries to pipe the same render to a second consumer; naive retry logic around shell errors.

Related errors


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