Link to the code that reproduces this issue
https://github.com/abir-taheer/next-js-readable-stream-bug
To Reproduce
git clone https://github.com/abir-taheer/next-js-readable-stream-bug
npm install && npm run dev
bash test.sh
Current vs. Expected behavior
Current: POST requests that pass through middleware returning NextResponse.next() hang indefinitely when the downstream handler reads the body via Readable.toWeb(). The request never completes and times out.
Expected: The request body should be fully readable by the downstream handler regardless of how it consumes the stream. Readable.toWeb() is the standard Node.js API for converting a Readable to a Web ReadableStream — it should work on the IncomingMessage after middleware runs.
Provide environment information
Operating System:
Platform: darwin
Arch: arm64
Version: Darwin Kernel Version 25.5.0: Mon Apr 27 20:41:12 PDT 2026; root:xnu-12377.121.6~2/RELEASE_ARM64_T6050
Available memory (MB): 65536
Available CPU cores: 18
Binaries:
Node: 24.17.0
npm: 11.13.0
Yarn: N/A
pnpm: 10.34.4
Relevant Packages:
next: 16.2.6
eslint-config-next: N/A
react: 19.2.7
react-dom: 19.2.7
typescript: 6.0.3
Next.js Config:
output: N/A
Which area(s) are affected? (Select all that apply)
Middleware
Which stage(s) are affected? (Select all that apply)
next dev (local), next start (local)
Additional context
The root cause is in replaceRequestBody() (body-streams.ts), not in middleware itself. After middleware calls NextResponse.next() on a POST route, runMiddleware clones the body into a PassThrough (Duplex) and copies its properties back onto the IncomingMessage (Readable) via for...in. This copies _writableState and 39 Writable prototype methods onto the Readable. Node's Readable.toWeb() then sees _writableState.finished === false and hangs waiting for the writable side to close.
The timing is deterministic, _writableState.finished becomes true via process.nextTick(), but replaceRequestBody() runs in a microtask (from await endPromise in finalize()). Microtasks always run before nextTick, so it always copies finished === false regardless of body size.
NextResponse.rewrite() is unaffected because it creates a new internal request and doesn't go through replaceRequestBody(). GET requests are also fine since there's no body to clone. The issue only affects POST (or PUT/PATCH) through NextResponse.next().
Link to the code that reproduces this issue
https://github.com/abir-taheer/next-js-readable-stream-bug
To Reproduce
git clone https://github.com/abir-taheer/next-js-readable-stream-bugnpm install && npm run devbash test.shCurrent vs. Expected behavior
Current: POST requests that pass through middleware returning NextResponse.next() hang indefinitely when the downstream handler reads the body via
Readable.toWeb(). The request never completes and times out.Expected: The request body should be fully readable by the downstream handler regardless of how it consumes the stream.
Readable.toWeb()is the standard Node.js API for converting a Readable to a Web ReadableStream — it should work on the IncomingMessage after middleware runs.Provide environment information
Operating System: Platform: darwin Arch: arm64 Version: Darwin Kernel Version 25.5.0: Mon Apr 27 20:41:12 PDT 2026; root:xnu-12377.121.6~2/RELEASE_ARM64_T6050 Available memory (MB): 65536 Available CPU cores: 18 Binaries: Node: 24.17.0 npm: 11.13.0 Yarn: N/A pnpm: 10.34.4 Relevant Packages: next: 16.2.6 eslint-config-next: N/A react: 19.2.7 react-dom: 19.2.7 typescript: 6.0.3 Next.js Config: output: N/AWhich area(s) are affected? (Select all that apply)
Middleware
Which stage(s) are affected? (Select all that apply)
next dev (local), next start (local)
Additional context
The root cause is in
replaceRequestBody()(body-streams.ts), not in middleware itself. After middleware callsNextResponse.next()on a POST route,runMiddlewareclones the body into a PassThrough (Duplex) and copies its properties back onto theIncomingMessage(Readable) viafor...in. This copies_writableStateand 39 Writable prototype methods onto the Readable.Node's Readable.toWeb()then sees_writableState.finished === falseand hangs waiting for the writable side to close.The timing is deterministic,
_writableState.finishedbecomes true viaprocess.nextTick(), butreplaceRequestBody()runs in a microtask (fromawait endPromiseinfinalize()). Microtasks always run before nextTick, so it always copiesfinished === falseregardless of body size.NextResponse.rewrite()is unaffected because it creates a new internal request and doesn't go throughreplaceRequestBody(). GET requests are also fine since there's no body to clone. The issue only affects POST (or PUT/PATCH) throughNextResponse.next().