cspNonce
The Content-Security-Policy nonce for the inline scripts you render, when [security] csp uses {nonce}.
import { cspNonce } from '@gio.js/core';
<script nonce={cspNonce()} dangerouslySetInnerHTML={{ __html: 'window.dataLayer = []' }} />Reference
cspNonce() takes no arguments.
Returns
string | undefined. A string - 32 lowercase hex characters - when [security] csp or csp_report_only contains {nonce}; undefined when neither does, during gio export, and in the browser.
Behavior
The string is not the nonce itself but a secret placeholder. Pages are cached and partial-prerendering shells replayed, so a render cannot know the nonce of the response that will carry it. The worker renders the placeholder; the Rust server replaces every occurrence with the response's fresh nonce - in the headers and the body of every dynamic response, cache hits included - just before sending it. The placeholder never reaches a browser.
- Every response gets a new random nonce of 192 bits (32 base64 characters).
- Pages, route handlers and event streams are all rewritten, whatever their content type.
public/files and build assets are served untouched. - While nonces are on, a dynamic response that sets its own
Content-Encodingcannot be searched and is refused with a500; let GioJS compress it instead.
Examples
An inline script in the root layout
[security]
csp = "default-src 'self'; script-src 'self' 'nonce-{nonce}' 'strict-dynamic'; style-src 'self' 'unsafe-inline'"import { cspNonce } from '@gio.js/core';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<head>
<script
nonce={cspNonce()}
dangerouslySetInnerHTML={{
__html: "document.documentElement.dataset.theme = localStorage.getItem('theme') ?? 'light';",
}}
/>
<script nonce={cspNonce()} async src="https://analytics.example.com/script.js" />
</head>
<body>{children}</body>
</html>
);
}The response carries the same nonce in its header and in both tags, for example Content-Security-Policy: ... script-src 'self' 'nonce-p849UZ8c5U9tiKEsDNv+B31bEFGrqj+z' ... and <script nonce="p849UZ8c5U9tiKEsDNv+B31bEFGrqj+z"> - and a new one on the next request, even when the page comes from the cache.
Good to know
- Pass it straight to a
nonceattribute. Hash, slice or encode the value and the server can no longer find it to replace. - Use it in server-rendered markup. In the browser it returns
undefined, so the root layout - which never hydrates - is the place for inline scripts. - Framework scripts are covered already: the hydration bootstrap, React's streaming scripts, the deployment script, the critical-CSS loader and the development overlay all carry the nonce without your help.
- Styles. Keep
style-src 'self' 'unsafe-inline':styleprops cannot carry a nonce. - CSP stays opt-in. A nonce is per response, so pages served with one are sent
private, no-cachewithout anETag: CDNs and browsers stop caching them, while the server's own page cache keeps working. - Rotation. The placeholder changes with a new
GIO_DEPLOYMENT_ID, a new standalone build or a change to thegio.tomlsettings pages render with - not with a code-only redeploy. To rotate it by hand, deletemeta/csp-nonce-placeholder-*in the page cache directory (.gio/cache/pagesunless[cache] disk_pathorGIO_CACHE_DIRmoves it) and restart.
Related
Version history
| Version | Changes |
|---|---|
v0.1.0-beta.8 | Introduced. |