Routes that need a no-inline Content Security Policy can opt into FaceTheory’s strict CSP path: no inline scripts, no inline styles, no raw head HTML, with hydration data moved to a same-origin sidecar instead of an inline __FACETHEORY_DATA__ block.
Opting in
Set FaceRenderResult.csp to disable the inline channels:
return {
html: '<h1>Hello</h1>',
csp: {
inlineScripts: false,
inlineStyles: false,
rawHead: false,
},
hydration: externalHydrationForEntry(
manifest,
'src/entry-client.ts',
data,
{ dataUrl: '/hydration/hello.json' },
),
};
When csp.inlineScripts === false, FaceTheory refuses to emit inline <script> bodies. Hydration must be external already or be externalized through a configured framework-owned path (SSR hydration sidecars, SSG build sidecars, or ISR sidecars); otherwise inline or Vite hydration fails closed.
Building the CSP header
import { buildStrictCspHeader, createCspNonce } from '@theory-cloud/facetheory';
const nonce = createCspNonce();
const cspHeader = buildStrictCspHeader({ cspNonce: nonce });
buildStrictCspHeader emits the canonical strict directive set:
default-src 'self'
base-uri 'self'
object-src 'none'
frame-ancestors 'none'
script-src 'self'
style-src 'self'
img-src 'self' data:
font-src 'self'
connect-src 'self'
form-action 'self'
Nonces are unique per response and consistent within a response so that <script nonce> attributes match the Content-Security-Policy header.
Document validation
For defense in depth, FaceTheory can validate the rendered document against the policy before emitting it:
import {
validateStrictCspDocument,
requiresStrictCspDocumentValidation,
} from '@theory-cloud/facetheory';
if (requiresStrictCspDocumentValidation(policy)) {
validateStrictCspDocument(html, { policy });
}
The validator throws on inline <script> bodies, inline style attributes, and raw head HTML that the policy forbids.
Sidecar hydration
Strict CSP requires hydration data, when present, to be external. Use one of:
- Framework-owned same-origin sidecars — configure
createFaceApp({ ssrHydrationSidecars })and return normalviteHydrationForEntry()data from the SSR Face. FaceTheory writes the exact render-time payload once before emitting a same-origin/_facetheory/ssr-data/...link. - Caller-managed external sidecars — use
externalHydrationForEntry()when the host owns the same-origin JSON URL.
See SSR hydration sidecars for the full path.
Examples in the repo
ts/examples/vite-strict-csp-svelte/— strict CSP delivery with Svelte + Vite