Summary
Next.js's automatic script nonce reads the nonce from the content-security-policy request header in app-render (parseRequestHeaders). Setting it from middleware via NextResponse.next({ request: { headers } }) works under next dev (every script gets the nonce) but not on @opennextjs/cloudflare.
Symptom
Same code, same middleware:
| Env |
page <script> |
with nonce |
next dev |
30 |
30 / 30 |
production (@opennextjs/cloudflare) |
19 |
1 / 19 (only an app inline script that reads a custom x-nonce header) |
So a strict script-src 'self' 'nonce-…' 'strict-dynamic' policy can't be enforced — every framework script would be blocked.
Root cause (traced end to end)
- Middleware runs as a separate function; the forwarded
Request is rebuilt by the edge converter — overrides/converters/edge.js convertTo, new Request(url, { headers }). Cloudflare's workerd strips content-security-policy (and content-security-policy-report-only) request headers at this reconstruction. Custom headers (e.g. x-nonce) survive.
- Restoring
content-security-policy from a surviving header inside the converter's convertFrom (on the plain-object headers) does not help either — it is stripped again before Next's app-render reads it (verified by encoding the restored value into the emitted nonce and observing it never reaches the HTML in production).
Node/undici's new Request() keeps these headers, so this is workerd-specific, not a Fetch-spec forbidden request header.
Reproduce
- Middleware sets a per-request nonce and
requestHeaders.set('content-security-policy', "script-src 'nonce-'+nonce+' 'strict-dynamic'"), returns NextResponse.next({ request: { headers: requestHeaders } }).
- Render any App-Router page; inspect the HTML in production vs
next dev: framework <script> tags lack the nonce in production.
Versions
@opennextjs/cloudflare 1.20.1, @opennextjs/aws 4.0.2, next 16.2.9, wrangler 4.106, compatibility_date 2024-12-30, nodejs_compat.
Suggested fix
Carry the content-security-policy request header through to the server render in a workerd-safe way (e.g. restore it after the final Request reconstruction, or expose the middleware nonce to the server render in a channel Next can read), so Next's automatic nonce works on Cloudflare as it does under next dev.
Summary
Next.js's automatic script nonce reads the nonce from the
content-security-policyrequest header inapp-render(parseRequestHeaders). Setting it from middleware viaNextResponse.next({ request: { headers } })works undernext dev(every script gets the nonce) but not on@opennextjs/cloudflare.Symptom
Same code, same middleware:
<script>next dev@opennextjs/cloudflare)x-nonceheader)So a strict
script-src 'self' 'nonce-…' 'strict-dynamic'policy can't be enforced — every framework script would be blocked.Root cause (traced end to end)
Requestis rebuilt by the edge converter —overrides/converters/edge.jsconvertTo,new Request(url, { headers }). Cloudflare's workerd stripscontent-security-policy(andcontent-security-policy-report-only) request headers at this reconstruction. Custom headers (e.g.x-nonce) survive.content-security-policyfrom a surviving header inside the converter'sconvertFrom(on the plain-object headers) does not help either — it is stripped again before Next'sapp-renderreads it (verified by encoding the restored value into the emitted nonce and observing it never reaches the HTML in production).Node/undici'snew Request()keeps these headers, so this is workerd-specific, not a Fetch-spec forbidden request header.Reproduce
requestHeaders.set('content-security-policy', "script-src 'nonce-'+nonce+' 'strict-dynamic'"), returnsNextResponse.next({ request: { headers: requestHeaders } }).next dev: framework<script>tags lack the nonce in production.Versions
@opennextjs/cloudflare1.20.1,@opennextjs/aws4.0.2,next16.2.9,wrangler4.106,compatibility_date2024-12-30,nodejs_compat.Suggested fix
Carry the
content-security-policyrequest header through to the server render in a workerd-safe way (e.g. restore it after the finalRequestreconstruction, or expose the middleware nonce to the server render in a channel Next can read), so Next's automatic nonce works on Cloudflare as it does undernext dev.