Urgent.News

What's breaking now, across thousands of outlets.

Tech

Content Security Policy in the Next.js App Router: Field Notes on Nonces, strict-dynamic, and the Middleware That Made Every Page Dynamic

A Content Security Policy (CSP) is an HTTP response header that tells the browser which script, style, and connection sources a document is allowed to use, and in the Next.js App Router the only script policy that survives the framework's runtime chunk loading is a per-request nonce combined with 'strict-dynamic' . The cost I did not budget for: generating that nonce in middleware.ts…

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.

Key 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.

The 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.

Initially, 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.

Unlike 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.

The 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.

Written by urgent.news from Dev.to's reporting — not their text. Machine-written — may contain errors; check the original before relying on it.

Read the original at dev.to →

More in Tech

Ever encountered a race condition bug when fetching data, let's understand the solution

We have all been there before. We build a sleek interface, wire up our API endpoints, and test the application under ideal local development conditions. Everything seems blazing fast and silky smooth.

  • Race conditions in web apps cause UI desynchronization with real data
  • Asynchronous operations fetch data without freezing UI
  • AbortController API cancels obsolete requests to prevent stale data

Automated Cybersecurity Update

{ "article": { "title": "CVE‑2026‑55040: SharePoint JWT Bypass Exploited in the Wild", "body_markdown": "🚨 Summary : The newly disclosed CVE-2026-55040 flaw in Microsoft SharePoint allows…

More from Friday 14 August →