Next.js CSP Configuration
Content Security Policy tells the browser which resources are allowed to load and execute. Without one, any XSS vulnerability can load scripts from anywhere.
Why Next.js Needs Special Handling
Next.js injects inline scripts for hydration, page data, and route transitions. A naive CSP that blocks all inline scripts will break your app. The solution is nonce-based CSP.
Reference: Next.js — Content Security Policy
Nonce-Based CSP with Middleware
Generate a unique nonce per request in middleware. In App Router, Next.js extracts the nonce from the request CSP header during server-side rendering and applies it to Next.js-managed scripts and styles, but this only works for dynamically rendered pages and when the CSP header includes a nonce-... token.
// middleware.ts
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
export function middleware(request: NextRequest) {
const nonce = btoa(crypto.randomUUID());
const csp = [
`default-src 'self'`,
`script-src 'self' 'nonce-${nonce}' 'strict-dynamic'`,
`style-src 'self' 'unsafe-inline'`,
`img-src 'self' data: https:`,
`font-src 'self'`,
`connect-src 'self'`,
`frame-ancestors 'none'`,
`base-uri 'self'`,
`form-action 'self'`,
`upgrade-insecure-requests`,
].join("; ");
const requestHeaders = new Headers(request.headers);
requestHeaders.set("x-nonce", nonce);
const response = NextResponse.next({
request: { headers: requestHeaders },
});
response.headers.set("Content-Security-Policy", csp);
return response;
}
Reading the Nonce in Server Components
Access the nonce via the request headers and pass it to components that need it:
// app/layout.tsx
import { headers } from "next/headers";
export default async function RootLayout({ children }: { children: React.ReactNode }) {
const headersList = await headers();
const nonce = headersList.get("x-nonce") ?? "";
return (
<html lang="en">
<body>
{children}
<script nonce={nonce} src="/analytics.js" />
</body>
</html>
);
}
Directive Breakdown
| Directive | Value | Purpose |
|---|---|---|
default-src | 'self' | Baseline: only allow resources from your origin |
script-src | 'self' 'nonce-...' 'strict-dynamic' | Allow own scripts + nonced inline scripts; strict-dynamic allows scripts loaded by trusted scripts |
style-src | 'self' 'unsafe-inline' | Next.js and CSS-in-JS often require inline styles |
img-src | 'self' data: https: | Allow images from your origin, data URIs, and HTTPS sources |
font-src | 'self' | Only load fonts from your origin |
connect-src | 'self' | Restrict fetch/XHR/WebSocket to your origin |
frame-ancestors | 'none' | Prevent your site from being embedded in iframes (clickjacking protection) |
base-uri | 'self' | Prevent <base> tag injection |
form-action | 'self' | Restrict form submission targets |
Reference: MDN — Content-Security-Policy
Common Adjustments
Third-Party Scripts (Analytics, Ads)
Add the domain to script-src and connect-src:
script-src 'self' 'nonce-...' 'strict-dynamic' https://www.googletagmanager.com;
connect-src 'self' https://www.google-analytics.com;
External Images (CDN, User Avatars)
Add the CDN domain to img-src:
img-src 'self' data: https://cdn.example.com;
WebSocket Connections
Add wss: to connect-src:
connect-src 'self' wss://your-websocket-domain.com;
Report-Only Mode
Start with Content-Security-Policy-Report-Only to collect violations without breaking anything:
response.headers.set("Content-Security-Policy-Report-Only", csp);
Add a reporting endpoint:
report-uri /api/csp-report;
report-to default;
Monitor violations, fix legitimate sources, then switch to enforcing mode.
Reference: MDN — Content-Security-Policy-Report-Only
Testing CSP
- Open browser DevTools → Console. CSP violations appear as errors.
- Use Google CSP Evaluator to check your policy.
- Run Lighthouse — it flags missing or weak CSP.
Common Mistakes
- Using
unsafe-inlineinscript-src: defeats the purpose of CSP. Use nonces instead. - Using
unsafe-eval: allowseval()which attackers exploit. Avoid unless a dependency absolutely requires it. - Overly permissive
connect-src:connect-src *lets XSS exfiltrate data anywhere. - Forgetting
frame-ancestors: without it, your site can be framed for clickjacking. - Not testing after deployment: CSP can behave differently in production due to CDN or proxy headers.
Related Guides
Content is AI-assisted and reviewed by our team, but issues may be missed and best practices evolve rapidly, send corrections to [email protected]. Always consult official documentation and validate key implementation decisions before making design or security choices.
