How to set a Content Security Policy (CSP) for your Next.js application
Content Security Policy (CSP) is important to guard your Next.js application against various security threats such as cross-site scripting (XSS), clickjacking, and other code injection attacks.
By using CSP, developers can specify which origins are permissible for content sources, scripts, stylesheets, images, fonts, objects, media (audio, video), iframes, and more.
Examples
Nonces
A nonce is a unique, random string of characters created for a one-time use. It is used in conjunction with CSP to selectively allow certain inline scripts or styles to execute, bypassing strict CSP directives.
Why use a nonce?
CSP can block both inline and external scripts to prevent attacks. A nonce lets you safely allow specific scripts to run—only if they include the matching nonce value.
If an attacker wanted to load a script into your page, they'd need to guess the nonce value. That's why the nonce must be unpredictable and unique for every request.
Adding a nonce with Middleware
Middleware enables you to add headers and generate nonces before the page renders.
Every time a page is viewed, a fresh nonce should be generated. This means that you must use dynamic rendering to add nonces.
For example:
import { NextRequest, NextResponse } from 'next/server'
export function middleware(request: NextRequest) {
const nonce = Buffer.from(crypto.randomUUID()).toString('base64')
const cspHeader = `
default-src 'self';
script-src 'self' 'nonce-${nonce}' 'strict-dynamic';
style-src 'self' 'nonce-${nonce}';
img-src 'self' blob: data:;
font-src 'self';
object-src 'none';
base-uri 'self';
form-action 'self';
frame-ancestors 'none';
upgrade-insecure-requests;
`
// Replace newline characters and spaces
const contentSecurityPolicyHeaderValue = cspHeader
.replace(/\s{2,}/g, ' ')
.trim()
const requestHeaders = new Headers(request.headers)
requestHeaders.set('x-nonce', nonce)
requestHeaders.set(
'Content-Security-Policy',
contentSecurityPolicyHeaderValue
)
const response = NextResponse.next({
request: {
headers: requestHeaders,
},
})
response.headers.set(
'Content-Security-Policy',
contentSecurityPolicyHeaderValue
)
return response
}
By default, Middleware runs on all requests. You can filter Middleware to run on specific paths using a matcher
.
We recommend ignoring matching prefetches (from next/link
) and static assets that don't need the CSP header.
export const config = {
matcher: [
/*
* Match all request paths except for the ones starting with:
* - api (API routes)
* - _next/static (static files)
* - _next/image (image optimization files)
* - favicon.ico (favicon file)
*/
{
source: '/((?!api|_next/static|_next/image|favicon.ico).*)',
missing: [
{ type: 'header', key: 'next-router-prefetch' },
{ type: 'header', key: 'purpose', value: 'prefetch' },
],
},
],
}
How nonces work in Next.js
To use a nonce, your page must be dynamically rendered. This is because Next.js applies nonces during server-side rendering, based on the CSP header present in the request. Static pages are generated at build time, when no request or response headers exist—so no nonce can be injected.
Here’s how nonce support works in a dynamically rendered page:
- Middleware generates a nonce: Your middleware creates a unique nonce for the request, adds it to your
Content-Security-Policy
header, and also sets it in a customx-nonce
header. - Next.js extracts the nonce: During rendering, Next.js parses the
Content-Security-Policy
header and extracts the nonce using the'nonce-{value}'
pattern. - Nonce is applied automatically: Next.js attaches the nonce to:
- Framework scripts (React, Next.js runtime)
- Page-specific JavaScript bundles
- Inline styles and scripts generated by Next.js
- Any
<Script>
components using thenonce
prop
Because of this automatic behavior, you don’t need to manually add a nonce to each tag.
Forcing dynamic rendering
If you're using nonces, you may need to explicitly opt pages into dynamic rendering:
import { connection } from 'next/server'
export default async function Page() {
// wait for an incoming request to render this page
await connection()
// Your page content
}
Reading the nonce
You can read the nonce from a Server Component using headers
:
import { headers } from 'next/headers'
import Script from 'next/script'
export default async function Page() {
const nonce = (await headers()).get('x-nonce')
return (
<Script
src="https://www.googletagmanager.com/gtag/js"
strategy="afterInteractive"
nonce={nonce}
/>
)
}
Static vs Dynamic Rendering with CSP
Using nonces has important implications for how your Next.js application renders:
Dynamic Rendering Requirement
When you use nonces in your CSP, all pages must be dynamically rendered. This means:
- Pages will build successfully but may encounter runtime errors if not properly configured for dynamic rendering
- Each request generates a fresh page with a new nonce
- Static optimization and Incremental Static Regeneration (ISR) are disabled
- Pages cannot be cached by CDNs without additional configuration
- Partial Prerendering (PPR) is incompatible with nonce-based CSP since static shell scripts won't have access to the nonce
Performance Implications
The shift from static to dynamic rendering affects performance:
- Slower initial page loads: Pages must be generated on each request
- Increased server load: Every request requires server-side rendering
- No CDN caching: Dynamic pages cannot be cached at the edge by default
- Higher hosting costs: More server resources needed for dynamic rendering
When to use nonces
Consider nonces when:
- You have strict security requirements that prohibit
'unsafe-inline'
- Your application handles sensitive data
- You need to allow specific inline scripts while blocking others
- Compliance requirements mandate strict CSP
Without Nonces
For applications that do not require nonces, you can set the CSP header directly in your next.config.js
file:
const cspHeader = `
default-src 'self';
script-src 'self' 'unsafe-eval' 'unsafe-inline';
style-src 'self' 'unsafe-inline';
img-src 'self' blob: data:;
font-src 'self';
object-src 'none';
base-uri 'self';
form-action 'self';
frame-ancestors 'none';
upgrade-insecure-requests;
`
module.exports = {
async headers() {
return [
{
source: '/(.*)',
headers: [
{
key: 'Content-Security-Policy',
value: cspHeader.replace(/\n/g, ''),
},
],
},
]
},
}
Subresource Integrity (Experimental)
As an alternative to nonces, Next.js offers experimental support for hash-based CSP using Subresource Integrity (SRI). This approach allows you to maintain static generation while still having a strict CSP.
Good to know: This feature is experimental and only available with webpack bundler in App Router applications.
How SRI works
Instead of using nonces, SRI generates cryptographic hashes of your JavaScript files at build time. These hashes are added as integrity
attributes to script tags, allowing browsers to verify that files haven't been modified during transit.
Enabling SRI
Add the experimental SRI configuration to your next.config.js
:
/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
sri: {
algorithm: 'sha256', // or 'sha384' or 'sha512'
},
},
}
module.exports = nextConfig
CSP configuration with SRI
When SRI is enabled, you can continue using your existing CSP policies. SRI works independently by adding integrity
attributes to your assets:
Good to know: For dynamic rendering scenarios, you can still generate nonces with middleware if needed, combining both SRI integrity attributes and nonce-based CSP approaches.
const cspHeader = `
default-src 'self';
script-src 'self';
style-src 'self';
img-src 'self' blob: data:;
font-src 'self';
object-src 'none';
base-uri 'self';
form-action 'self';
frame-ancestors 'none';
upgrade-insecure-requests;
`
module.exports = {
experimental: {
sri: {
algorithm: 'sha256',
},
},
async headers() {
return [
{
source: '/(.*)',
headers: [
{
key: 'Content-Security-Policy',
value: cspHeader.replace(/\n/g, ''),
},
],
},
]
},
}
Benefits of SRI over nonces
- Static generation: Pages can be statically generated and cached
- CDN compatibility: Static pages work with CDN caching
- Better performance: No server-side rendering required for each request
- Build-time security: Hashes are generated at build time, ensuring integrity
Limitations of SRI
- Experimental: Feature may change or be removed
- Webpack only: Not available with Turbopack
- App Router only: Not supported in Pages Router
- Build-time only: Cannot handle dynamically generated scripts
Development vs Production Considerations
CSP implementation differs between development and production environments:
Development Environment
In development, you will need to enable 'unsafe-eval'
to support APIs that provide additional debugging information:
export function middleware(request: NextRequest) {
const nonce = Buffer.from(crypto.randomUUID()).toString('base64')
const isDev = process.env.NODE_ENV === 'development'
const cspHeader = `
default-src 'self';
script-src 'self' 'nonce-${nonce}' 'strict-dynamic' ${isDev ? "'unsafe-eval'" : ''};
style-src 'self' 'nonce-${nonce}' ${isDev ? "'unsafe-inline'" : ''};
img-src 'self' blob: data:;
font-src 'self';
object-src 'none';
base-uri 'self';
form-action 'self';
frame-ancestors 'none';
upgrade-insecure-requests;
`
// Rest of middleware implementation
}
Production Deployment
Common issues in production:
- Nonce not applied: Ensure your middleware runs on all necessary routes
- Static assets blocked: Verify your CSP allows Next.js static assets
- Third-party scripts: Add necessary domains to your CSP policy
Troubleshooting
Third-party Scripts
When using third-party scripts with CSP:
import { GoogleTagManager } from '@next/third-parties/google'
import { headers } from 'next/headers'
export default async function RootLayout({
children,
}: {
children: React.ReactNode
}) {
const nonce = (await headers()).get('x-nonce')
return (
<html lang="en">
<body>
{children}
<GoogleTagManager gtmId="GTM-XYZ" nonce={nonce} />
</body>
</html>
)
}
Update your CSP to allow third-party domains:
const cspHeader = `
default-src 'self';
script-src 'self' 'nonce-${nonce}' 'strict-dynamic' https://www.googletagmanager.com;
connect-src 'self' https://www.google-analytics.com;
img-src 'self' data: https://www.google-analytics.com;
`
Common CSP Violations
- Inline styles: Use CSS-in-JS libraries that support nonces or move styles to external files
- Dynamic imports: Ensure dynamic imports are allowed in your script-src policy
- WebAssembly: Add
'wasm-unsafe-eval'
if using WebAssembly - Service workers: Add appropriate policies for service worker scripts
Version History
Version | Changes |
---|---|
v14.0.0 | Experimental SRI support added for hash-based CSP |
v13.4.20 | Recommended for proper nonce handling and CSP header parsing. |
Next Steps
Was this helpful?