What happens
On a page without :index, anything style- or script-shaped passed as :head is silently refused by the CSP. The page renders unstyled and the browser logs a CSP violation, with nothing pointing back at :head as the cause.
Why
csp sets style-src 'nonce-<nonce>' — no 'self', no 'unsafe-inline'. So a <style> block, an inline <script>, or a <link rel="stylesheet"> is admitted only when it carries that nonce.
The nonce is reachable from :index but not from :head:
rendered-page ends with (str/replace html "NONCE" nonce), so an index file writes nonce="NONCE" and gets the real value.
generated-page concatenates head verbatim. There is no substitution, and the nonce is not exposed to the caller in any other way.
The result is that :head cannot carry styles or inline scripts at all on a generated page. An external script still works, since script-src includes 'self', which makes the failure look inconsistent: your <script src=...> runs, your <style> does not.
Related, and probably worth a line of docs either way: rendered-page destructures only :index and :mounts, so :head is ignored outright once :index is set.
Repro
(buzz/handler {:mounts [{:el \"app\" :ui #'my-app}]
:head \"<style>body { background: #111; color: #eee }</style>\"})
The page renders with default styling. Console shows the style block refused by CSP.
Suggested fix
Substitute in head the same way rendered-page does for the whole document, so :head can use the literal NONCE:
(str/replace (or head \"\") \"NONCE\" nonce)
That keeps one convention for both page paths. An alternative, if :head should stay literal, is to accept a function of the nonce.
Workaround
Switch to :index and put the styles in the file with nonce=\"NONCE\", as public/index.html already does. That works, but it costs you the generated page: the mount elements now have to be listed by hand in the HTML, which drifts as soon as a mount is added.
What happens
On a page without
:index, anything style- or script-shaped passed as:headis silently refused by the CSP. The page renders unstyled and the browser logs a CSP violation, with nothing pointing back at:headas the cause.Why
cspsetsstyle-src 'nonce-<nonce>'— no'self', no'unsafe-inline'. So a<style>block, an inline<script>, or a<link rel="stylesheet">is admitted only when it carries that nonce.The nonce is reachable from
:indexbut not from:head:rendered-pageends with(str/replace html "NONCE" nonce), so an index file writesnonce="NONCE"and gets the real value.generated-pageconcatenatesheadverbatim. There is no substitution, and the nonce is not exposed to the caller in any other way.The result is that
:headcannot carry styles or inline scripts at all on a generated page. An external script still works, sincescript-srcincludes'self', which makes the failure look inconsistent: your<script src=...>runs, your<style>does not.Related, and probably worth a line of docs either way:
rendered-pagedestructures only:indexand:mounts, so:headis ignored outright once:indexis set.Repro
The page renders with default styling. Console shows the style block refused by CSP.
Suggested fix
Substitute in
headthe same wayrendered-pagedoes for the whole document, so:headcan use the literalNONCE:That keeps one convention for both page paths. An alternative, if
:headshould stay literal, is to accept a function of the nonce.Workaround
Switch to
:indexand put the styles in the file withnonce=\"NONCE\", aspublic/index.htmlalready does. That works, but it costs you the generated page: the mount elements now have to be listed by hand in the HTML, which drifts as soon as a mount is added.