· 2 min read

CSP without inline scripts: what had to be switched off in Nuxt

Why a strict script-src self policy breaks static Nuxt, which three steps made the site compatible, and the build check that keeps it so.

Project: landscape-bureau-nuxt

The policy script-src 'self' without unsafe-inline closes a whole class of XSS: any inline script simply does not run. You pay for it in compatibility with tools that like to inject code into HTML.

What Nuxt does by default

A static build puts an inline script with window.__NUXT__ and an importmap into the page, and inlines styles. Under a strict policy the browser blocks them and the page is left without hydration: the markup is there, the interactivity is not. The portfolio site itself had this: the fixed CSP on the VPS blocked hydration until the importmap was dropped from the export and the Nuxt config was moved into a file.

What was done in landscape-bureau-nuxt

  1. experimental.entryImportMap is off in nuxt.config.ts.
  2. Inline styles are off: features.inlineStyles: false.
  3. A postgenerate script moves window.__NUXT__ into a file _nuxt/inline-<hash>.js.

So it does not come back

check-security inspects the generated HTML and fails the build if it finds an executable inline script, a <style> or style="". The same trick is in the React project bike-workshop-react (the linter bans style={}) and in ceramics-vue.

The result: CSP script-src 'self' and style-src 'self', HSTS, nosniff and frame-ancestors 'none' work together with a live site on Cloudflare Pages. This is a concept with a fictional bureau, and I did not run Lighthouse.