{
  "id": 841605,
  "title": "Content Security Policy in the Next.js App Router: Field Notes on Nonces, strict-dynamic, and the Middleware That Made Every Page Dynamic",
  "url": "https://urgent.news/2026/08/14/content-security-policy-in-the-next-js-app-router-field-notes-on",
  "topic": "tech",
  "section": "Tech",
  "published": "2026-08-14T06:02:54.000Z",
  "source": {
    "name": "Dev.to",
    "slug": "dev-to",
    "url": "https://dev.to/ahmed_mahmoud360/content-security-policy-in-the-nextjs-app-router-field-notes-on-nonces-strict-dynamic-and-the-16d5"
  },
  "original_language": "en",
  "account": "A Content Security Policy (CSP) is an HTTP response header that dictates which sources a document may utilize for scripts, styles, and connections. In the Next.js App Router, the sole surviving script policy during chunk loading is a nonce per request, coupled with strict-dynamic. The challenge lay in generating that nonce in middleware.ts, then reading it through headers() opts for every matched route to deviate from static rendering.\n\nKey insights reveal that a CSP nonce is a unique token for each request, appearing in both the script-src directive and as an attribute on allowed scripts. Due to its non-repeatability, HTML containing a nonce cannot be cached, which is precisely why Next.js opts for dynamic rendering instead of static. A host allowlist cannot secure a Next.js app. The App Router emits an inline bootstrap payload and subsequently generates further script elements at runtime, rendering script-src self ineffective in both allowing the inline payload and distinguishing framework chunks from other same-origin files.\n\nThe strict-dynamic directive propagates trust from a nonce-allowed script to any script element the script creates programmatically, making chunk loading work without enumerating chunk URLs. It is crucial to retain unsafe-inline and https: in script-src as a deliberate fallback, not a vulnerability. The enforcing header transforms a policy mistake into a blank page, while the report-only header converts the same mistake into a log line.\n\nInitially, a CSP does not prevent injection; it stops execution. If an attacker injects script fetch(/api/me) /script into a comment field rendered with dangerouslySetInnerHTML, the markup still lands in the DOM. However, a policy without unsafe-inline means the browser refuses to run it and logs a violation instead. Four directives proved sufficient before touching script-src: object-src none removes the legacy plugin vector, base-uri self prevents injected base tags from silently redirecting all relative URLs, form-action self stops injected forms from posting credentials to another origin, and frame-ancestors none replaces X-Frame-Options. These four directives can reside in next.config.js under headers() and remain cacheable. The script-src directive, requiring a nonce, is the costly one.\n\nUnlike a domain allowlist, Next.js needs a nonce to accommodate the App Router's runtime behavior. It serializes the React Server Component payload into inline script tags on the document, then creates additional script elements at runtime to fetch route chunks. script-src self blocks the inline payload outright, and even if it did not, self would execute any same-origin URL, including user-uploaded files served from the same domain. strict-dynamic is a CSP Level 3 keyword that resolves this issue by stating that any script element crafted by an already-trusted script inherits that trust. The inline bootstrap inherits trust from the nonce, and all subsequent chunks it loads inherit it too. No chunk hashes or build-time URL enumeration are required.\n\nThe paradox lies in what strict-dynamic disables. In browsers implementing it, all host-source expressions in the same directive — self, https:, a literal CDN domain — are disregarded. This is why the recommended policy still includes them: they serve as a safety net for older browsers, not as additional permissions for modern ones. Generating and propagating a CSP nonce in the App Router involves creating it once per request within middleware.ts, writing it to both request and response headers, and reading it back in a Server Component using headers(). Writing it onto the request is vital, as Next.js looks for a content-security-policy request header, extracts the nonce, and applies it to the emitted script tags. Skipping this step blocks Next.js's own bootstrap due to the policy.",
  "summary": "The article discusses the use of Content Security Policy (CSP) in Next.js applications, specifically focusing on the App Router. It highlights that the only script policy that survives runtime chunk loading in Next.js is a per-request nonce combined with strict-dynamic. CSP serves as an HTTP response header that dictates which sources a document can use for scripts, styles, and connections. The article emphasizes that a CSP nonce is a per-request random token appearing both in the script-src directive and as a nonce attribute on every allowed script. It explains that because a nonce cannot repeat, HTML carrying a nonce cannot be cached, leading to Next.js dropping the route to dynamic rendering. The article also mentions that a host allowlist cannot secure a Next.js app, as the App Router emits an inline bootstrap payload and creates further script elements at runtime. The use of strict-dynamic is crucial for propagating trust from a nonce-allowed script to any script element that the script creates programmatically, enabling chunk loading without enumerating chunk URLs.",
  "key_points": [],
  "editors_take": null,
  "illustration": null,
  "coverage": {
    "outlets": 1,
    "also_reported_by": []
  },
  "ai_generated": true,
  "disclaimer": "Summaries, key points and the editor’s take are written by software from other outlets’ reporting and may contain errors — always check the linked original."
}