vercel / vercel/next.js

App Router: boundary (loading/template/error) chunk <script> is emitted without the CSP nonce

Open Beginner friendly
#98,015 2 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

Loading UI and Streaming
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
  1. npm install && npm run build && npm start
  2. 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.
  3. The page suspends for one second, so React streams the loading.tsx fallback — and the boundary's entry scripts — into the initial HTML.
  4. 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 for template / error / loading boundaries, called from create-component-tree.js) builds the <script> without nonce.
  • server/app-render/get-layer-assets.js (layout / page layer) builds the identical element with nonce: 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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.