App Router: boundary (loading/template/error) chunk <script> is emitted without the CSP nonce
Nobody has claimed this yet.
- Dominant language
- JavaScript
- Stars
- 142k
- Forks
- 32.4k
- Avg merge
- 2d 14h
- Merged PRs (30d)
- 351
Description
Link to the code that reproduces this issue
https://github.com/Vatroslav/next-csp-nonce-repro
To Reproduce
npm install && npm run build && npm start- Open http://localhost:3000/repro as a full page load (type the URL / hard reload, not a client-side navigation), with the browser console open.
- The page suspends for one second, so React streams the
loading.tsxfallback — and the boundary's entry scripts — into the initial HTML. - Observe the CSP violation in the console. Or check the served HTML directly:
curl -s http://localhost:3000/repro | grep -o '<script[^>]*>' | grep -v nonce
Current vs. Expected behavior
The app uses the nonce-based CSP from the official guide (script-src 'self' 'nonce-…' 'strict-dynamic', no 'unsafe-inline'), with the nonce generated per request in proxy.ts.
Expected: every <script> Next.js emits during SSR carries the request nonce, as the CSP guide states.
Actual: exactly one script tag — the loading segment's entry chunk — has no nonce attribute and is blocked:
<!-- layout / page layer assets: nonce present -->
<script src="/_next/static/chunks/3fntmmi971322.js" async="" nonce="…"></script>
<!-- loading boundary asset: nonce missing, blocked -->
<script src="/_next/static/chunks/1dwh5sf9shrqz.js" async=""></script>
Console (Chrome 148):
Loading the script 'http://localhost:3000/_next/static/chunks/1dwh5sf9shrqz.js' violates the following Content Security Policy directive: "script-src 'self' 'nonce-MWJjMGJkYzctMGQ5ZC00NzgyLThjOWQtMGU2MTc0ZDI1NjQ0' 'strict-dynamic'". Note that 'strict-dynamic' is present, so host-based allowlisting is disabled. The action has been blocked.
The failure is mostly cosmetic when the same chunk also reaches the page another way, but any app combining loading.tsx/template.tsx/error.tsx client chunks with the documented strict CSP gets console violations and a wasted request on every affected page load.
Provide environment information
Operating System:
Platform: win32
Arch: x64
Version: Windows 11 Pro
Binaries:
Node: 22.14.0
npm: 11.12.0
pnpm: 11.1.3
Relevant Packages:
next: 16.3.3 // Latest available version is detected (16.3.3).
react: 19.2.8
react-dom: 19.2.8
typescript: 5.9.3
Next.js Config:
output: N/A
Which area(s) are affected? (Select all that apply)
Loading UI and Streaming
Which stage(s) are affected? (Select all that apply)
next start (local), Other (Deployed)
Additional context
Root cause — two sibling modules build the same script element, and only one passes the nonce:
server/app-render/create-component-styles-and-scripts.js(used fortemplate/error/loadingboundaries, called fromcreate-component-tree.js) builds the<script>withoutnonce.server/app-render/get-layer-assets.js(layout / page layer) builds the identical element withnonce: ctx.nonce.
The boundary scripts are rendered as part of the Suspense fallback in client/components/layout-router.js, so on a full page load they are parser-inserted markup and 'strict-dynamic' cannot vouch for them. (On client-side navigations React inserts them via appendChild, which 'strict-dynamic' allows — that's why the bug only shows on document loads.)
Suggested fix: add nonce: ctx.nonce to the createElement('script', …) call in create-component-styles-and-scripts, mirroring get-layer-assets.
Verified on 16.2.11, 16.3.3 and 16.4.0-canary.10 by running this reproduction — the source asymmetry is identical in all three published packages.
The repro README also documents two effects that mask the bug in common app shapes (chunk already in injectedJS; flight-preload dedupe when the boundary shares a rendered client reference), which is likely why it's easy to build an app that doesn't show it.
Prior reports: #94680, #96605 and #97882 describe this same defect and were closed by the triage bot for having no public reproduction link, without being triaged on the merits. This issue supplies that link.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with server/app-render/create-component-styles-and-scripts.js and compare its boundary script creation with server/app-render/get-layer-assets.js, following the call from create-component-tree.js. Use the linked reproduction and run its build/start commands, then verify with the documented curl command that boundary scripts carry the request nonce without violating CSP.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript, next.js, react
- Domain
- full-stack, web-dev
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 84/100