Content Security Policy: A Practical Guide for 2026
Content Security Policy is the most powerful browser security control — and the most complex to implement. This guide walks through CSP for both simple sites and Next.js/React applications.
What CSP protects against
Content Security Policy is a browser security standard that controls which resources your page is allowed to load. Its primary purpose is preventing cross-site scripting (XSS) — where an attacker injects malicious JavaScript into your page, which then runs with your page's origin privileges.
A properly configured CSP means that even if an attacker finds an XSS injection point in your application, the browser will refuse to execute injected scripts, preventing cookie theft, credential harvesting, and account takeover.
The basics
CSP is an HTTP response header:
Content-Security-Policy: default-src 'self';
This tells the browser: by default, only load resources from the same origin as the page. Scripts, stylesheets, images, fonts, and connections to external APIs from anywhere else will be blocked.
In practice, your CSP will be more complex than this because most sites use some external resources. Build it incrementally:
Content-Security-Policy: default-src 'self'; script-src 'self' https://www.googletagmanager.com; style-src 'self' https://fonts.googleapis.com 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' https://fonts.gstatic.com; connect-src 'self' https://api.example.com; frame-ancestors 'none'; base-uri 'self';
Report-only mode
Before enforcing CSP, use report-only mode to discover what would be blocked:
Content-Security-Policy-Report-Only: default-src 'self'; report-uri /csp-report
The browser logs all violations to your report endpoint without blocking anything. Review reports for 1–2 weeks to discover legitimate resources you have not whitelisted.
The nonce approach for dynamic sites
If you use inline scripts (common in React/Next.js for hydration), 'unsafe-inline' in script-src defeats much of CSP's XSS protection. The nonce approach lets you allow specific inline scripts without opening the door to injected ones.
Generate a random nonce per request, include it in the header, and add it to trusted script tags:
// HTTP header Content-Security-Policy: script-src 'nonce-abc123xyz'; // HTML <script nonce="abc123xyz">/* only this script runs */</script>
In Next.js, implement nonce generation in middleware and pass it through via headers. The Next.js documentation covers this pattern in detail for the App Router.
frame-ancestors vs X-Frame-Options
The frame-ancestors CSP directive controls where your page can be embedded in an iframe. It supersedes X-Frame-Options in modern browsers but you should include both for complete coverage:
Content-Security-Policy: frame-ancestors 'none'; X-Frame-Options: DENY
Common CSP mistakes
'unsafe-inline'inscript-src— defeats XSS protection; use nonces instead'unsafe-eval'inscript-src— allows eval() which is a common XSS vector- Wildcards like
*.googleapis.com— an attacker who can serve content from any subdomain of googleapis.com can bypass your policy - Missing
base-uri 'self'— without this, an injected<base>tag can redirect relative URLs to an attacker's server - Missing
frame-ancestors— leaves you vulnerable to clickjacking
Checking your CSP
VP Shield checks your Mozilla Observatory score which covers CSP along with other security headers. An Observatory grade below B usually indicates missing or weak CSP. The scan is free and takes under two minutes.