Skip to content

fix(cloudflare-node): use native IdentityTransformStream to prevent intermittent mid-stream response stalls#1196

Open
Einherjar1632 wants to merge 1 commit into
opennextjs:mainfrom
Einherjar1632:fix/cloudflare-node-native-stream
Open

fix(cloudflare-node): use native IdentityTransformStream to prevent intermittent mid-stream response stalls#1196
Einherjar1632 wants to merge 1 commit into
opennextjs:mainfrom
Einherjar1632:fix/cloudflare-node-native-stream

Conversation

@Einherjar1632

Copy link
Copy Markdown

Summary

The cloudflare-node wrapper builds the streamed response body from a JS-backed ReadableStream with a manually captured controller, and Writable.write acknowledges chunks immediately without backpressure (controller.desiredSize is never consulted).

On deployed Workers (production runtime, not reproducible with wrangler dev/local workerd) we observed this intermittently stalling mid-stream: the final flush(es) of chunked SSR/RSC responses are never delivered to the client and the terminating chunk is never sent, so the connection stays open indefinitely. Browsers keep waiting on the incomplete response, gradually exhaust their per-origin connection pool, and the whole site eventually freezes for that browser profile (every request stuck in "Stalled", even sign-out).

This PR switches the response body to the Workers-native (C++-backed) IdentityTransformStream and awaits writer.write(), so the runtime pumps the stream itself with real end-to-end backpressure.

Reproduction / evidence

Observed on a production app (Next.js 16.2.10, @opennextjs/cloudflare 1.20.1, wrangler 4.107.0, compatibility_date 2025-12-01, free plan, custom domain). No auth required — a plain RSC request to the login page reproduces it:

$ for i in $(seq 1 20); do curl --http1.1 -s -o /dev/null -m 8 \
    -w "%{time_total}s %{size_download}B\n" -H "RSC:1" https://<app>/login; done
0.14s 8599B      # ok — complete flight payload, stream closed
8.00s 6934B      # HUNG — stalls at the same byte offset every time,
...              #        terminating chunk never arrives (7/20 in this run)

Validation

Measured on the same production deployment, patched adapter only (no other changes):

JS ReadableStream (current) IdentityTransformStream (this PR)
RSC response hangs 4–7 / 20 0 / 60
HTML document hangs 2 / 15 0 / 20
Real-browser SPA navigation (Playwright) froze at the 5th navigation 300 navigations, no freeze, no lingering pending requests
  • pnpm build (tsc) passes, pnpm lint (biome) clean, tests-unit 600/600 pass.
  • IdentityTransformStream is declared locally in the file (it is a Workers-specific global); this wrapper only runs on the Workers runtime.

Notes

  • writer.write() resolving only when the chunk is accepted also gives the wrapper real backpressure, which the previous implementation lacked (unbounded queue growth in the JS stream).
  • Non-Uint8Array chunks are converted with Buffer.from(chunk, encoding) since IdentityTransformStream only accepts bytes.
  • The null-body-status fast path is unchanged.

🤖 Generated with Claude Code

… body

The JS-backed ReadableStream with a manually captured controller
intermittently stalls mid-stream on deployed Workers: the final flushes
are never delivered to the client and the terminating chunk is never
sent, leaving connections open indefinitely. Writes were acknowledged
without backpressure, so the stall was invisible to the worker.

Switch to the Workers-native IdentityTransformStream and await
writer.write() so the runtime pumps the stream with real backpressure.
@changeset-bot

changeset-bot Bot commented Jul 9, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: ff98bd4

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
@opennextjs/aws Patch
app-pages-router Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant