Context
Every public page can be generated at build time. The site must still send a restrictive Content Security
Policy. App Router pages contain inline scripts that carry the React Server Components payload. A
script-src 'self' policy blocks them, and Next.js's nonce-based CSP requires every page to be
dynamically rendered.
Options
'unsafe-inline'scripts. Static pages, but the CSP gives little protection against script injection.- Nonces from the Next.js proxy. A strong CSP, but every page renders per request, Nginx cannot cache the HTML, and Partial Prerendering is disabled.
- Hashes computed after the build. Static pages and a strong CSP, at the cost of a post-build step and an Nginx map file.
Decision
After next build, scripts/csp-hashes.ts reads every prerendered HTML file. It computes the SHA-256 of
each inline script and writes an Nginx map from request path to that route's script-src hashes. Nginx
sends script-src 'self' 'sha256-…' for known routes. For any route not in the map it sends the
strictest policy: the server-rendered HTML still displays, and only client-side enhancements are blocked.
Root-layout ensureStatic = 'navigation' makes the build fail if any page becomes dynamic. That keeps the
hashes complete.
Consequences
- No
'unsafe-inline'for scripts. - The map is a build artifact, regenerated on every build. Playwright runs through Nginx and fails on any CSP violation, so a missing hash is caught in CI.
- Inline
styleattributes are not used anywhere, sostyle-src 'self'holds.