Environment variable and secret handling guidelines
Always treat environment variable values as sensitive unless they are known test-mode flags. Never print or paste secret values in chat, commits, or shared logs. Mirror CI env names and modes exactly, but do not inline literal secret values in commands. If a required secret is missing locally, stop and ask the user rather than inventing placeholder credentials. Never commit local secret files.
Content Security Policy purpose and scope
Content Security Policy (CSP) guards Next.js applications against security threats such as cross-site scripting (XSS), clickjacking, and code injection attacks. CSP allows developers to specify which origins are permissible for content sources including scripts, stylesheets, images, fonts, objects, media (audio, video), iframes, and more.
Nonce definition and purpose
A nonce is a unique, random string of characters created for one-time use. It is used with CSP to selectively allow certain inline scripts or styles to execute while bypassing strict CSP directives. The nonce must be unpredictable and unique for every request—if an attacker wanted to load a script into a page, they would need to guess the nonce value.
Dynamic rendering requirement for nonces
When using nonces in CSP, pages must be dynamically rendered 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. Each request must generate a fresh page with a new nonce.
Nonce generation with Proxy
Proxy enables you to add headers and generate nonces before the page renders. Every time a page is viewed, a fresh nonce should be generated, which requires using dynamic rendering. The proxy function creates a unique nonce for each request, adds it to the Content-Security-Policy header, and also sets it in a custom x-nonce header.
Proxy nonce example with TypeScript
Example proxy implementation in TypeScript:
```ts
import { NextRequest, NextResponse } from 'next/server'
export function proxy(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}';
img-src 'self' blob: data:;
font-src 'self';
object-src 'none';
base-uri 'self';
form-action 'self';
frame-ancestors 'none';
upgrade-insecure-requests;
`
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
}
```
Proxy nonce example with JavaScript
Example proxy implementation in JavaScript:
```js
import { NextResponse } from 'next/server'
export function proxy(request) {
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}';
img-src 'self' blob: data:;
font-src 'self';
object-src 'none';
base-uri 'self';
form-action 'self';
frame-ancestors 'none';
upgrade-insecure-requests;
`
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
}
```
Proxy matcher configuration to exclude prefetches and static assets
The proxy matcher configuration should exclude API routes, static files, image optimization files, and favicon, and also filter out prefetch requests:
```ts
export const config = {
matcher: [
{
source: '/((?!api|_next/static|_next/image|favicon.ico).*)',
missing: [
{ type: 'header', key: 'next-router-prefetch' },
{ type: 'header', key: 'purpose', value: 'prefetch' },
],
},
],
}
```
This ignores matching prefetches from next/link and static assets that don't need the CSP header.
How nonce support works in Next.js
Nonce support in dynamically rendered pages works as follows: 1) Proxy generates a unique nonce for the request, adds it to the Content-Security-Policy header, and sets it in a custom x-nonce header. 2) During rendering, Next.js parses the Content-Security-Policy header and extracts the nonce using the 'nonce-{value}' pattern. 3) Next.js attaches the nonce automatically to framework scripts (React, Next.js runtime), page-specific JavaScript bundles, inline styles and scripts generated by Next.js, and any Script components using the nonce prop. Because of this automatic behavior, you do not need to manually add a nonce to each tag.
Using connection() to force dynamic rendering for nonces
To force dynamic rendering when using nonces, you can use the connection() function in a Server Component:
```tsx
import { connection } from 'next/server'
export default async function Page() {
await connection()
// Your page content
}
```
The connection() function waits for an incoming request to render the page, ensuring dynamic rendering is used.
Reading nonce in App Router with headers()
In App Router, you can read the nonce from a Server Component using the headers() function:
```tsx
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}
/>
)
}
```
Performance implications of dynamic rendering with nonces
Using nonces requires dynamic rendering, which has performance implications: pages must be generated on each request causing slower initial page loads, increased server load as every request requires server-side rendering, no CDN caching as dynamic pages cannot be cached at the edge by default, and higher hosting costs as more server resources are needed for dynamic rendering.
Partial Prerendering incompatibility with nonce-based CSP
Partial Prerendering (PPR) is incompatible with nonce-based CSP because static shell scripts will not have access to the nonce. When using nonces, PPR cannot be used.
When to use nonces in CSP
Consider using 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, or compliance requirements mandate strict CSP.
CSP without nonces using next.config.js
For applications that do not require nonces, you can set the CSP header directly in next.config.js using the headers() configuration:
```js
const isDev = process.env.NODE_ENV === 'development'
const cspHeader = `
default-src 'self';
script-src 'self' 'unsafe-inline'${isDev ? " 'unsafe-eval'" : ''};
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 (SRI) for hash-based CSP
Next.js offers experimental support for hash-based CSP using Subresource Integrity (SRI), available in App Router applications. SRI generates cryptographic hashes of JavaScript files at build time and adds them as integrity attributes to script tags, allowing browsers to verify that files have not been modified during transit. This approach allows static generation while maintaining strict CSP.
Enabling SRI in next.config.js
Add the experimental SRI configuration to next.config.js:
```js
/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
sri: {
algorithm: 'sha256', // or 'sha384' or 'sha512'
},
},
}
module.exports = nextConfig
```
SRI configuration with CSP in next.config.js
When SRI is enabled, you can continue using existing CSP policies. SRI works independently by adding integrity attributes to assets:
```js
const isDev = process.env.NODE_ENV === 'development'
const cspHeader = `
default-src 'self';
script-src 'self'${isDev ? " 'unsafe-eval'" : ''};
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
SRI provides several benefits compared to nonces: pages can be statically generated and cached, static pages work with CDN caching, no server-side rendering is required for each request providing better performance, and hashes are generated at build time ensuring integrity.
Limitations of SRI
SRI has the following limitations: the feature is experimental and may change or be removed, it is only supported in App Router and not in Pages Router, and hashes are generated at build time so it cannot handle dynamically generated scripts.
unsafe-eval requirement in development
In development, you must enable 'unsafe-eval' because React uses eval to provide enhanced debugging information such as reconstructing server-side error stacks in the browser to show where errors originated on the server. 'unsafe-eval' is not required for production as neither React nor Next.js use eval in production by default.
Development vs production CSP considerations
CSP implementation differs between development and production. In development, 'unsafe-eval' must be enabled for React's debugging features. In production, common issues include nonce not being applied (ensure proxy runs on all necessary routes), static assets being blocked (verify CSP allows Next.js static assets), and third-party scripts (add necessary domains to CSP policy).
Using third-party scripts with CSP in App Router
When using third-party scripts with CSP in App Router, pass the nonce from headers():
```tsx
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>
)
}
```
CSP header configuration for third-party scripts
Update your CSP to allow third-party domains:
```ts
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 and solutions
Common CSP violations include: inline styles (use CSS-in-JS libraries that support nonces or move styles to external files), dynamic imports (ensure dynamic imports are allowed in script-src policy), WebAssembly (add 'wasm-unsafe-eval' if using WebAssembly), and service workers (add appropriate policies for service worker scripts).
CSP version history for Next.js
Version history for CSP support: v14.0.0 added experimental SRI support for hash-based CSP, v13.4.20 is recommended for proper nonce handling and CSP header parsing.