Skip to content
Ali Akbari
Menu

ADR-0002 · accepted · 8 October 2026

Static pages with a build-time, per-route hash CSP

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 style attributes are not used anywhere, so style-src 'self' holds.

Source: docs/adr/0002-static-pages-with-hashed-csp.md

← All decisions