Every website e2e run logs 37-46 copies of ⨯ Error: The destination stream closed early., all with one digest. All 112 tests pass and no user-visible symptom exists. Filing so nobody re-derives this.
Cause — confirmed upstream and reproduced locally
A known, open Next.js regression: vercel/next.js#96704, reported against 16.3.1-canary.3 / react 19.2.8. Also #96465 (auto-closed for lacking a repro, not on the merits). Two fix PRs open, neither merged: #96715, #96717. No maintainer has commented on either thread.
A community bisection varying only next: 16.2.11 clean, 16.3.0 reproduces, 16.3.1-canary.9 still reproduces. 16.3.0 is where __NEXT_USE_NODE_STREAMS was switched on, routing RSC through pipeNodeReadableToNodeResponse. Verified in our own install:
dist/build/define-env.js:92 — hardcoded true, no config opt-out.
dist/server/pipe-readable.js:150-153 — res.once('close', () => readable.destroy()), destroy with no reason.
- React therefore synthesises
Error("The destination stream closed early."), name === "Error".
dist/server/pipe-readable.js:32-34 — isAbortError matches on name only (AbortError / ResponseAborted), so it does not silence it, and create-error-handler.js logs it.
Why a completed response never logs it
From React's source (react-server-dom-turbopack-server.node.development.js): pipe() registers destination.on("close", createCancelHandler(...)) at :6954, and abort() at :4409 is guarded by if (!(11 < request.status)). Successful renders set status = CLOSED (14) before calling destination.end() (:4326). So the later close hits the guard and is a silent no-op.
Every occurrence therefore means a Flight render was still in progress when its socket closed. There is no benign-completion path.
Local proof
A logging reverse proxy in front of next start, correlating socket closes with in-flight renders:
| proxy behaviour |
mid-stream client disconnects |
errors |
suite |
| pass through (destroy upstream) |
111 |
40 |
112/112 |
| absorb after headers |
67 absorbed, 42 destroyed pre-headers |
13 |
112/112 |
| absorb fully (drain, never destroy) |
103 absorbed |
0 |
112/112 |
Driving the early close to zero drives the error to zero. The aborted requests are overwhelmingly ?_rsc= prefetches of /docs/** — <Link> prefetches from the docs sidebar's 41 links, cancelled when Playwright tears a page down. /docs/[[...slug]] is the only request-time Flight render in a production build; everything else is prerendered.
Notably favicon.ico is a prerendered route handler, not a Flight render — the favicon test is implicated only by when it ends (immediately after waitForDocsReady, at the peak of the prefetch burst), not by what it fetches.
What we should do
Nothing, for now. There is no supported suppression: onRequestError is the only hook and we do not export one; the console line comes from Next's own logging with no documented filter. Pinning to 16.2.x would trade real fixes for log tidiness.
Watch vercel/next.js#96704 and drop this issue when it lands.
Related
#376 halves docs render time by removing a duplicate MDX+shiki compile. That narrows the window but does not fix this (34 lines after vs 37-46 before — noise).
Dead ends, so nobody repeats them
Each produced zero: complete HTML loads; complete RSC loads; curl aborts on both; Node socket-destroy on first byte across five route/mode combinations; real Chromium closing the context at 0/50/150/400/1200ms; cold vs warm server; hydration-then-teardown; prefetches allowed to settle; the query-stringed favicon URL repeated; request.get before page.goto. Patching Next's compiled runtime to log the request URL breaks it — destination is not bound at all three occurrences of the string; 30 tests fail. Use an external proxy instead.
Every website e2e run logs 37-46 copies of
⨯ Error: The destination stream closed early., all with one digest. All 112 tests pass and no user-visible symptom exists. Filing so nobody re-derives this.Cause — confirmed upstream and reproduced locally
A known, open Next.js regression: vercel/next.js#96704, reported against
16.3.1-canary.3/ react19.2.8. Also #96465 (auto-closed for lacking a repro, not on the merits). Two fix PRs open, neither merged: #96715, #96717. No maintainer has commented on either thread.A community bisection varying only
next:16.2.11clean,16.3.0reproduces,16.3.1-canary.9still reproduces. 16.3.0 is where__NEXT_USE_NODE_STREAMSwas switched on, routing RSC throughpipeNodeReadableToNodeResponse. Verified in our own install:dist/build/define-env.js:92— hardcodedtrue, no config opt-out.dist/server/pipe-readable.js:150-153—res.once('close', () => readable.destroy()), destroy with no reason.Error("The destination stream closed early."),name === "Error".dist/server/pipe-readable.js:32-34—isAbortErrormatches onnameonly (AbortError/ResponseAborted), so it does not silence it, andcreate-error-handler.jslogs it.Why a completed response never logs it
From React's source (
react-server-dom-turbopack-server.node.development.js):pipe()registersdestination.on("close", createCancelHandler(...))at :6954, andabort()at :4409 is guarded byif (!(11 < request.status)). Successful renders setstatus = CLOSED (14)before callingdestination.end()(:4326). So the later close hits the guard and is a silent no-op.Every occurrence therefore means a Flight render was still in progress when its socket closed. There is no benign-completion path.
Local proof
A logging reverse proxy in front of
next start, correlating socket closes with in-flight renders:Driving the early close to zero drives the error to zero. The aborted requests are overwhelmingly
?_rsc=prefetches of/docs/**—<Link>prefetches from the docs sidebar's 41 links, cancelled when Playwright tears a page down./docs/[[...slug]]is the only request-time Flight render in a production build; everything else is prerendered.Notably
favicon.icois a prerendered route handler, not a Flight render — the favicon test is implicated only by when it ends (immediately afterwaitForDocsReady, at the peak of the prefetch burst), not by what it fetches.What we should do
Nothing, for now. There is no supported suppression:
onRequestErroris the only hook and we do not export one; the console line comes from Next's own logging with no documented filter. Pinning to16.2.xwould trade real fixes for log tidiness.Watch vercel/next.js#96704 and drop this issue when it lands.
Related
#376 halves docs render time by removing a duplicate MDX+shiki compile. That narrows the window but does not fix this (34 lines after vs 37-46 before — noise).
Dead ends, so nobody repeats them
Each produced zero: complete HTML loads; complete RSC loads; curl aborts on both; Node socket-destroy on first byte across five route/mode combinations; real Chromium closing the context at 0/50/150/400/1200ms; cold vs warm server; hydration-then-teardown; prefetches allowed to settle; the query-stringed favicon URL repeated;
request.getbeforepage.goto. Patching Next's compiled runtime to log the request URL breaks it —destinationis not bound at all three occurrences of the string; 30 tests fail. Use an external proxy instead.