Skip to content

fix(shims): copy request headers into mutable Headers in headersContextFromRequest#128

Merged
southpolesteve merged 1 commit intocloudflare:mainfrom
harrisrobin:fix/immutable-headers
Mar 2, 2026
Merged

fix(shims): copy request headers into mutable Headers in headersContextFromRequest#128
southpolesteve merged 1 commit intocloudflare:mainfrom
harrisrobin:fix/immutable-headers

Conversation

@harrisrobin
Copy link
Copy Markdown
Contributor

Summary

In Cloudflare Workers, Request.headers is immutable per the Fetch API spec. headersContextFromRequest was storing the original request.headers reference directly in HeadersContext, so when applyMiddlewareRequestHeaders later called ctx.headers.set() (to propagate middleware-modified headers into server component context), Workers threw:

TypeError: Can't modify immutable headers.

This is triggered on every request when middleware returns NextResponse.next({ request: { headers } }) — a common pattern for passing data like x-request-url from middleware to server components.

Fix

Wrap with new Headers(request.headers), which creates an independent mutable copy while preserving all header values.

- headers: request.headers,
+ headers: new Headers(request.headers),

Test plan

  • New test asserting the context holds a mutable copy, not the original Request.headers reference
  • All 2026 existing tests pass
  • Lint: 0 warnings/errors (oxlint)
  • Typecheck: clean (tsgo --noEmit)

/bonk

…xtFromRequest

In Cloudflare Workers, Request.headers is immutable per the Fetch API spec.
headersContextFromRequest was storing the original request.headers reference
directly in HeadersContext, so when applyMiddlewareRequestHeaders later called
ctx.headers.set() (to propagate middleware-modified headers like x-request-url
into the server component context), Workers threw:

  TypeError: Can't modify immutable headers.

Fix by wrapping with new Headers(request.headers), which creates an independent
mutable copy while preserving all header values.
southpolesteve pushed a commit that referenced this pull request Feb 27, 2026
Co-authored-by: ask-bonk[bot] <ask-bonk[bot]@users.noreply.github.com>
@solracnyc
Copy link
Copy Markdown

Can confirm this bug in production. We hit the exact same TypeError: Can't modify immutable headers on Cloudflare Workers with vinext 0.0.15 + React 19.2.4.

Environment:

  • vinext 0.0.15 (also reproduced on 0.0.16)
  • React 19.2.4 / react-dom 19.2.4
  • Clerk middleware (clerkMiddleware from @clerk/nextjs/server) — triggers NextResponse.next({ request: { headers } }) on every request
  • Cloudflare Workers (not Pages)

Impact: Every route returned HTTP 500. The worker tail showed TypeError: Can't modify immutable headers on all requests.

Our workaround: We cloned the request with mutable headers in our worker entry point before passing to vinext's app-router handler:

// worker/index.ts
const requestWithMutableHeaders = new Request(request, {
  headers: new Headers(request.headers),
  cf: (request as Request & { cf?: unknown }).cf,
} as RequestInit);

return handler.fetch(requestWithMutableHeaders);

This is essentially the same fix as this PR but applied at the app level instead of inside vinext. Would be great to get this merged upstream so we can remove our workaround. Thanks @harrisrobin for filing this!

@southpolesteve
Copy link
Copy Markdown
Collaborator

/bigbonk review this. Did we already fix this on main? We might have

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Mar 2, 2026

Open in StackBlitz

npm i https://pkg.pr.new/vinext@128

commit: 4c3a34f

Copy link
Copy Markdown
Contributor

@ask-bonk ask-bonk Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clean, correct, and well-scoped fix. The change is minimal and targets exactly the right place.

Analysis:

  • headersContextFromRequest is called in app-dev-server.ts:1178 where the Request comes directly from the Cloudflare Workers runtime (immutable headers). Later, applyMiddlewareRequestHeaders (line 1354) calls .set() on ctx.headers, which blows up on Workers.

  • I checked the other places in the codebase that pass request.headers into context-like objects (app-dev-server.ts:1006 and config-matchers.ts:253). Both are read-only contexts used for has/missing condition matching — they only call .get(), never .set() — so they don't need the same treatment.

  • The Pages Router prod-server (prod-server.ts:792) also calls .set() on request headers, but it constructs its own new Request(...) from Node.js IncomingMessage headers, so those are already mutable. No issue there.

  • The test correctly verifies three properties: reference inequality, mutability, and isolation from the original request headers.

LGTM.

@ask-bonk
Copy link
Copy Markdown
Contributor

ask-bonk Bot commented Mar 2, 2026

Approved. The fix is correct and well-scoped. Here's the summary of my review:

  • The one-line change (new Headers(request.headers)) correctly creates a mutable copy, fixing the TypeError: Can't modify immutable headers crash on Cloudflare Workers.
  • I verified the other places in the codebase that use request.headers directly (app-dev-server.ts:1006 for __buildRequestContext and config-matchers.ts:253 for requestContextFromRequest) — both are read-only contexts that only call .get(), so they don't need the same fix.
  • The Pages Router prod-server (prod-server.ts:792) also calls .set() on headers, but constructs its Request from Node.js IncomingMessage with new Headers(), so those are already mutable.
  • The test covers reference inequality, mutability, and isolation from the original request — all the right things to verify.

github run

@southpolesteve southpolesteve merged commit 74f92ac into cloudflare:main Mar 2, 2026
20 checks passed
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.

3 participants