Guides
CSP
Content Security Policy limits which scripts, styles, images, frames, and network targets a page can use. In Mreact, route-owned CSP lives in metadata.csp so the policy is resolved with the same layout and page metadata that produces the document head.
What CSP controls
Mreact serializes metadata.csp into the Content-Security-Policy response header. Common directives include default-src, script-src, style-src, connect-src, img-src, font-src, frame-src, frame-ancestors, base-uri, form-action, and object-src.
Use CSP to reduce the blast radius of injected markup or unexpected third-party code. It is not a replacement for escaping, validation, authentication, CSRF checks, or dependency review.
Start with a route baseline
Put a conservative baseline in a shared layout. Page metadata can then narrow or override it for routes that need payments, analytics, previews, or embeds.
// src/app/layout.tsx
import type { RouteMetadata } from "@reckona/mreact-router";
export const metadata = {
csp: {
directives: {
"base-uri": ["'self'"],
"default-src": ["'self'"],
"font-src": ["'self'"],
"form-action": ["'self'"],
"frame-ancestors": ["'none'"],
"img-src": ["'self'", "data:"],
"object-src": ["'none'"],
"script-src": ["'self'"],
},
},
} satisfies RouteMetadata;Start with the smallest policy the route can actually satisfy. Add hosts only after you know which browser request needs them.
Do not add style-src: ["'self'"] until you know every inline style on the route is nonce-backed or external. Route-owned inline <style> descriptors should use metadata.csp.nonce with nonce: true, otherwise the browser will block them under a strict style-src.
Add nonces for route-owned inline code
Use a per-request nonce when a route needs inline executable code or inline style descriptors. Generate the nonce in generateMetadata(), put it in metadata.csp.nonce, and use nonce: true on route-owned metadata.head script or style descriptors.
// src/app/analytics/page.tsx
import { randomBytes } from "node:crypto";
import type {
GenerateMetadataContext,
RouteMetadata,
} from "@reckona/mreact-router";
function createNonce(): string {
return randomBytes(16).toString("base64url");
}
export function generateMetadata(
_context: GenerateMetadataContext,
): RouteMetadata {
const nonce = createNonce();
return {
csp: {
directives: {
"default-src": ["'self'"],
"script-src": ["'self'"],
"style-src": ["'self'"],
},
nonce,
},
head: [
{
tag: "script",
nonce: true,
content: "window.dataLayer=window.dataLayer||[];",
},
],
};
}Mreact appends the generated 'nonce-...' source expression to script-src and style-src, and it writes the nonce attribute onto descriptors with nonce: true. The nonce value must use a base64 or base64url alphabet.
Compose and override CSP
Layout and page CSP metadata compose. directives adds or overrides directive names, replace replaces inherited directive values, remove deletes inherited directives, and disable: true suppresses the route CSP header.
// src/app/layout.tsx
export const metadata = {
csp: {
directives: {
"default-src": ["'self'"],
"connect-src": ["'self'", "https://api.example.test"],
"report-uri": ["/csp-report"],
},
},
};// src/app/checkout/page.tsx
export const metadata = {
csp: {
replace: {
"connect-src": ["'self'", "https://pay.example.test"],
},
remove: ["report-uri"],
},
};// src/app/oauth/callback/page.tsx
export const metadata = {
csp: { disable: true },
};Use disable: true sparingly. It is usually better to narrow the inherited policy with replace and remove than to remove CSP for an entire route.
Inline scripts and styles
A nonce on script-src or style-src changes which inline tags the browser accepts. If an inline <script> or <style> does not have a matching nonce, the browser can block it.
Mreact warns in development through router:csp:inline-nonce-warning when route HTML contains blocked inline tags. The warning text includes inline <script> without a matching nonce or inline <style> without a matching nonce under a nonce-bearing directive.
Prefer external CSS for strict production CSP. Add inline scripts or styles through metadata.head with nonce: true, move them to external assets, or remove the nonce-bearing directive for routes that cannot satisfy it yet.
External services
Third-party integrations usually require more than one directive. GTM and GA4 can need script-src for loader scripts, connect-src for collection endpoints, img-src for beacon fallbacks, and sometimes frame-src for tag manager preview or embedded flows.
Add only the exact production hosts you need. Do not add broad schemes or wildcard hosts because a vendor snippet failed once in development. For route-owned script examples, see External Scripts.
Validate and roll out
Roll out CSP in stages:
- Start with a narrow policy on a low-risk route.
- Use browser DevTools and server logs to find blocked resources.
- Test critical flows such as login, checkout, uploads, analytics, previews, and embeds.
- Deploy a
Content-Security-Policy-Report-Onlyheader at the edge or server adapter when you need telemetry before enforcing a site-wide policy. - Move stable policy into
metadata.cspor deployment headers once violations are understood.
Do not build directives from user input. Mreact validates directive names, directive values, and nonce shape before serializing the header, but CSP should still be authored from trusted configuration.