GioJSdocs
On this page

cspNonce

The Content-Security-Policy nonce for the inline scripts you render, when [security] csp uses {nonce}.

tsx
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-Encoding cannot be searched and is refused with a 500; let GioJS compress it instead.

Examples

An inline script in the root layout

gio.toml
[security]
csp = "default-src 'self'; script-src 'self' 'nonce-{nonce}' 'strict-dynamic'; style-src 'self' 'unsafe-inline'"
app/layout.tsx
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 nonce attribute. 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': style props cannot carry a nonce.
  • CSP stays opt-in. A nonce is per response, so pages served with one are sent private, no-cache without an ETag: 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 the gio.toml settings pages render with - not with a code-only redeploy. To rotate it by hand, delete meta/csp-nonce-placeholder-* in the page cache directory (.gio/cache/pages unless [cache] disk_path or GIO_CACHE_DIR moves it) and restart.

Version history

VersionChanges
v0.1.0-beta.8Introduced.