Content Security Policy for Next.js: A Practical Guide
June 19, 2026
A Content Security Policy (Content Security PolicyA browser-enforced guest list that says which sources a page is allowed to run scripts, styles, and other resources from.If an attacker injects a <script> tag pointing at evil.com, the browser refuses to load it because evil.com isn't on the page's allow-list.) sounds simple — just tell the browser which sources to trust — but it breaks in surprising ways with modern frameworks. This guide explains what it is, why Next.js fights it, and the configuration that actually works in production.
The Core Problem
Next.js injects inline scripts to hydrate your pages (turning the server-rendered HTML into a live, interactive app). A strict script-src 'self' policy blocks these inline scripts, which breaks the site entirely in production. The fix is not obvious from the CSP spec alone.
Recommended Configuration
Set CSP as a production-only response header in next.config.ts:
const isDev = process.env.NODE_ENV !== "production";
const securityHeaders = [
{
key: "Content-Security-Policy",
value: isDev
? "" // Disabled in dev — Next.js HMR uses inline scripts
: [
"default-src 'self'",
"script-src 'self' 'unsafe-inline'",
"style-src 'self' 'unsafe-inline'",
"img-src 'self' data:",
"font-src 'self'",
"connect-src 'self'",
"frame-ancestors 'none'",
].join("; "),
},
];Why unsafe-inline for Scripts
Next.js inline hydration scripts do not have A fresh, single-use token the server stamps on each legitimate inline script so the browser can tell real scripts from injected ones. by default. The nonce-based approach requires per-request header generation in middleware, which adds significant complexity. 'unsafe-inline' is the pragmatic choice for most applications.
If you need stricter CSP for compliance requirements, Next.js supports nonce-based CSP through middleware — but that is a separate topic.
Why CSP Is Off in Dev
The Next.js dev server relies on inline scripts for:
- Hot Module Replacement (Hot Module ReplacementThe dev-server feature that swaps changed code into a running page instantly, without a full reload.Change a button's color in your editor and it updates on the page right away, without wiping the form you'd half-filled out.)
- Error overlay
- React fast refresh
Enabling CSP in dev blocks all of these and makes development impractical. Always guard the CSP header behind a NODE_ENV !== "production" check.
Cloudflare-Specific Entries
If your site sits behind Cloudflare (even in DNS-only mode), Cloudflare may inject scripts that your CSP will block unless explicitly allowed:
static.cloudflareinsights.com— the Web Analytics beacon scriptcloudflareinsights.com— the beacon data endpoint
Add both to the relevant directives:
"script-src 'self' 'unsafe-inline' https://static.cloudflareinsights.com",
"connect-src 'self' https://cloudflareinsights.com",If you are deploying to Cloudflare Pages with a static export, put these headers in public/_headers instead of next.config.ts — the Pages edge serves those headers directly.
The Full Security Header Set
CSP alone is not enough. Pair it with:
{ key: "Strict-Transport-Security", value: "max-age=31536000; includeSubDomains" },
{ key: "X-Content-Type-Options", value: "nosniff" },
{ key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
{ key: "Permissions-Policy", value: "camera=(), microphone=(), geolocation=()" },
{ key: "X-Frame-Options", value: "DENY" },Each one closes a different door:
- HTTP Strict Transport SecurityA response header that tells the browser to only ever connect to your site over HTTPS, never plain HTTP.Type http://yourbank.com and the browser upgrades it to https:// before sending anything, so open WiFi never carries an unencrypted request. — always take the locked HTTPS road, never the open one. Once a browser has seen this header, it refuses to connect to your site over plain HTTP, even if someone tries to downgrade the connection.
- X-Content-Type-Options: nosniff — read the label on the box; don't guess what's inside. It stops the browser from second-guessing a file's type and, say, running an image as if it were a script.
- X-Frame-Options: DENY — nobody gets to frame your storefront inside theirs. It blocks other sites from embedding your pages in a hidden
<iframe>to trick users into clicking things they can't see (clickjacking).
Testing Your Headers
After deploying, check your headers with Security Headers or a quick curl -I https://yourdomain.com. A correctly configured site should score A or A+ — which simply means every door above is shut and the browser is being told, explicitly, what it's allowed to do.