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
- Guard pipe() with your own boolean and call it exactly once per request; create a new renderToPipeableStream request for any retry
- To fan out, pipe once into a Node PassThrough/Transform and tee from that stream
- 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
- Treat a PipeableStream as single-use: pipe it exactly once per request
- Centralize the pipe call in one place (e.g. onShellReady) instead of piping from multiple handlers
- For fan-out, pipe once into a PassThrough and tee downstream
- For retries, abort the old request and create a new renderToPipeableStream
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
- react-dom/server is not supported in React Server Components
- 426
- 417
- React currently only supports piping to one writable stream.
- Server Functions cannot be called during initial render. Thi
AI-assisted analysis of facebook/react@eafeac097b (2026-08-21).
Data as JSON: /api/errors/4827f0e7bdba52a6.
Report an issue: GitHub.