.
This commit is contained in:
+730
@@ -0,0 +1,730 @@
|
||||
---
|
||||
title: How to set a Content Security Policy (CSP) for your Next.js application
|
||||
nav_title: Content Security Policy
|
||||
description: Learn how to set a Content Security Policy (CSP) for your Next.js application.
|
||||
related:
|
||||
links:
|
||||
- app/api-reference/file-conventions/proxy
|
||||
- app/api-reference/functions/headers
|
||||
---
|
||||
|
||||
{/* The content of this doc is shared between the app and pages router. You can use the `<PagesOnly>Content</PagesOnly>` component to add content that is specific to the Pages Router. Any shared content should not be wrapped in a component. */}
|
||||
|
||||
[Content Security Policy (CSP)](https://developer.mozilla.org/docs/Web/HTTP/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.
|
||||
|
||||
<details>
|
||||
<summary>Examples</summary>
|
||||
|
||||
- [Strict CSP](https://github.com/vercel/next.js/tree/canary/examples/with-strict-csp)
|
||||
|
||||
</details>
|
||||
|
||||
## Nonces
|
||||
|
||||
A [nonce](https://developer.mozilla.org/docs/Web/HTML/Global_attributes/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 Proxy
|
||||
|
||||
[Proxy](/docs/app/api-reference/file-conventions/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. This means that you **must use [dynamic rendering](/docs/app/glossary#dynamic-rendering) to add nonces**.
|
||||
|
||||
For example:
|
||||
|
||||
> **Good to know**: In development, `'unsafe-eval'` is required because React uses `eval` to provide enhanced debugging information, such as reconstructing server-side error stacks in the browser. `unsafe-eval` is not required for production. Neither React nor Next.js use `eval` in production by default.
|
||||
|
||||
```ts filename="proxy.ts" switcher
|
||||
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;
|
||||
`
|
||||
// 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
|
||||
}
|
||||
```
|
||||
|
||||
```js filename="proxy.js" switcher
|
||||
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;
|
||||
`
|
||||
// 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, Proxy runs on all requests. You can filter Proxy to run on specific paths using a [`matcher`](/docs/app/api-reference/file-conventions/proxy#matcher).
|
||||
|
||||
We recommend ignoring matching prefetches (from `next/link`) and static assets that don't need the CSP header.
|
||||
|
||||
```ts filename="proxy.ts" switcher
|
||||
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' },
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
```js filename="proxy.js" switcher
|
||||
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:
|
||||
|
||||
1. **Proxy generates a nonce**: Your proxy creates a unique nonce for the request, adds it to your `Content-Security-Policy` header, and also sets it in a custom `x-nonce` header.
|
||||
2. **Next.js extracts the nonce**: During rendering, Next.js parses the `Content-Security-Policy` header and extracts the nonce using the `'nonce-{value}'` pattern.
|
||||
3. **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 the `nonce` 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:
|
||||
|
||||
```tsx filename="app/page.tsx" switcher
|
||||
import { connection } from 'next/server'
|
||||
|
||||
export default async function Page() {
|
||||
// wait for an incoming request to render this page
|
||||
await connection()
|
||||
// Your page content
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/page.jsx" switcher
|
||||
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
|
||||
|
||||
<PagesOnly>
|
||||
You can provide the nonce to your page using
|
||||
[`getServerSideProps`](/docs/pages/building-your-application/data-fetching/get-server-side-props):
|
||||
|
||||
```tsx filename="pages/index.tsx" switcher
|
||||
import Script from 'next/script'
|
||||
|
||||
import type { GetServerSideProps } from 'next'
|
||||
|
||||
export default function Page({ nonce }) {
|
||||
return (
|
||||
<Script
|
||||
src="https://www.googletagmanager.com/gtag/js"
|
||||
strategy="afterInteractive"
|
||||
nonce={nonce}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export const getServerSideProps: GetServerSideProps = async ({ req }) => {
|
||||
const nonce = req.headers['x-nonce']
|
||||
return { props: { nonce } }
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="pages/index.jsx" switcher
|
||||
import Script from 'next/script'
|
||||
export default function Page({ nonce }) {
|
||||
return (
|
||||
<Script
|
||||
src="https://www.googletagmanager.com/gtag/js"
|
||||
strategy="afterInteractive"
|
||||
nonce={nonce}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export async function getServerSideProps({ req }) {
|
||||
const nonce = req.headers['x-nonce']
|
||||
return { props: { nonce } }
|
||||
}
|
||||
```
|
||||
|
||||
You can also access the nonce in `_document.tsx` for Pages Router applications:
|
||||
|
||||
```tsx filename="pages/_document.tsx" switcher
|
||||
import Document, {
|
||||
Html,
|
||||
Head,
|
||||
Main,
|
||||
NextScript,
|
||||
DocumentContext,
|
||||
DocumentInitialProps,
|
||||
} from 'next/document'
|
||||
|
||||
interface ExtendedDocumentProps extends DocumentInitialProps {
|
||||
nonce?: string
|
||||
}
|
||||
|
||||
class MyDocument extends Document<ExtendedDocumentProps> {
|
||||
static async getInitialProps(
|
||||
ctx: DocumentContext
|
||||
): Promise<ExtendedDocumentProps> {
|
||||
const initialProps = await Document.getInitialProps(ctx)
|
||||
const nonce = ctx.req?.headers?.['x-nonce'] as string | undefined
|
||||
|
||||
return {
|
||||
...initialProps,
|
||||
nonce,
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { nonce } = this.props
|
||||
|
||||
return (
|
||||
<Html lang="en">
|
||||
<Head nonce={nonce} />
|
||||
<body>
|
||||
<Main />
|
||||
<NextScript nonce={nonce} />
|
||||
</body>
|
||||
</Html>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default MyDocument
|
||||
```
|
||||
|
||||
```jsx filename="pages/_document.jsx" switcher
|
||||
import Document, { Html, Head, Main, NextScript } from 'next/document'
|
||||
|
||||
class MyDocument extends Document {
|
||||
static async getInitialProps(ctx) {
|
||||
const initialProps = await Document.getInitialProps(ctx)
|
||||
const nonce = ctx.req?.headers?.['x-nonce']
|
||||
|
||||
return {
|
||||
...initialProps,
|
||||
nonce,
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { nonce } = this.props
|
||||
|
||||
return (
|
||||
<Html lang="en">
|
||||
<Head nonce={nonce} />
|
||||
<body>
|
||||
<Main />
|
||||
<NextScript nonce={nonce} />
|
||||
</body>
|
||||
</Html>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default MyDocument
|
||||
```
|
||||
|
||||
</PagesOnly>
|
||||
|
||||
<AppOnly>
|
||||
|
||||
You can read the nonce from a [Server Component](/docs/app/getting-started/server-and-client-components) using [`headers`](/docs/app/api-reference/functions/headers):
|
||||
|
||||
```tsx filename="app/page.tsx" switcher
|
||||
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}
|
||||
/>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/page.jsx" switcher
|
||||
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}
|
||||
/>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
</AppOnly>
|
||||
|
||||
## 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`](/docs/app/api-reference/config/next-config-js) file:
|
||||
|
||||
```js filename="next.config.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, ''),
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
<AppOnly>
|
||||
|
||||
## 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 available 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`:
|
||||
|
||||
```js filename="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 proxy if needed, combining both SRI integrity attributes and nonce-based CSP approaches.
|
||||
|
||||
```js filename="next.config.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
|
||||
|
||||
- **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
|
||||
|
||||
</AppOnly>
|
||||
|
||||
## Development vs Production Considerations
|
||||
|
||||
CSP implementation differs between development and production environments:
|
||||
|
||||
### Development Environment
|
||||
|
||||
In development, you will need to enable `'unsafe-eval'` because React uses `eval` to provide enhanced debugging information, such as reconstructing server-side error stacks in the browser to show you where errors originated on the server:
|
||||
|
||||
```ts filename="proxy.ts" switcher
|
||||
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' ${isDev ? "'unsafe-inline'" : `'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;
|
||||
`
|
||||
|
||||
// Rest of proxy implementation
|
||||
}
|
||||
```
|
||||
|
||||
```js filename="proxy.js" switcher
|
||||
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' ${isDev ? "'unsafe-inline'" : `'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;
|
||||
`
|
||||
|
||||
// Rest of proxy implementation
|
||||
}
|
||||
```
|
||||
|
||||
### Production Deployment
|
||||
|
||||
Common issues in production:
|
||||
|
||||
- **Nonce not applied**: Ensure your proxy 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
|
||||
|
||||
<AppOnly>
|
||||
|
||||
When using third-party scripts with CSP:
|
||||
|
||||
```tsx filename="app/layout.tsx" switcher
|
||||
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>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/layout.jsx" switcher
|
||||
import { GoogleTagManager } from '@next/third-parties/google'
|
||||
import { headers } from 'next/headers'
|
||||
|
||||
export default async function RootLayout({ children }) {
|
||||
const nonce = (await headers()).get('x-nonce')
|
||||
|
||||
return (
|
||||
<html lang="en">
|
||||
<body>
|
||||
{children}
|
||||
<GoogleTagManager gtmId="GTM-XYZ" nonce={nonce} />
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
</AppOnly>
|
||||
|
||||
<PagesOnly>
|
||||
|
||||
When using third-party scripts with CSP, ensure you add the necessary domains and pass the nonce:
|
||||
|
||||
```tsx filename="pages/_app.tsx" switcher
|
||||
import type { AppProps } from 'next/app'
|
||||
import Script from 'next/script'
|
||||
|
||||
export default function App({ Component, pageProps }: AppProps) {
|
||||
const nonce = pageProps.nonce
|
||||
|
||||
return (
|
||||
<>
|
||||
<Component {...pageProps} />
|
||||
<Script
|
||||
src="https://www.googletagmanager.com/gtag/js"
|
||||
strategy="afterInteractive"
|
||||
nonce={nonce}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="pages/_app.jsx" switcher
|
||||
import Script from 'next/script'
|
||||
|
||||
export default function App({ Component, pageProps }) {
|
||||
const nonce = pageProps.nonce
|
||||
|
||||
return (
|
||||
<>
|
||||
<Component {...pageProps} />
|
||||
<Script
|
||||
src="https://www.googletagmanager.com/gtag/js"
|
||||
strategy="afterInteractive"
|
||||
nonce={nonce}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
</PagesOnly>
|
||||
|
||||
Update your CSP to allow third-party domains:
|
||||
|
||||
```ts filename="proxy.ts" switcher
|
||||
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;
|
||||
`
|
||||
```
|
||||
|
||||
```js filename="proxy.js" switcher
|
||||
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
|
||||
|
||||
1. **Inline styles**: Use CSS-in-JS libraries that support nonces or move styles to external files
|
||||
2. **Dynamic imports**: Ensure dynamic imports are allowed in your script-src policy
|
||||
3. **WebAssembly**: Add `'wasm-unsafe-eval'` if using WebAssembly
|
||||
4. **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. |
|
||||
Reference in New Issue
Block a user