CSP

Fix CSP Header in Next.js — Nonce-Based Approach

Next.js generates inline scripts for hydration that a strict CSP will block. Adding unsafe-inline fixes the error but defeats the purpose of CSP entirely. Nonces are the right solution — a random value per request that lets specific scripts run.

Browser Console Error
Refused to execute inline script because it violates the following Content Security Policy directive: "script-src 'self'". Either the 'unsafe-inline' keyword, a hash, or a nonce is required to enable inline execution.

Step 1 — Generate nonce in middleware.ts

// middleware.ts
import { NextResponse } from 'next/server';
import crypto from 'crypto';

export function middleware(request) {
  const nonce = Buffer.from(crypto.randomUUID()).toString('base64');

  const csp = [
    "default-src 'self'",
    `script-src 'self' 'nonce-${nonce}' 'strict-dynamic'`,
    "style-src 'self' 'unsafe-inline'",
    "img-src 'self' data: https:",
    "font-src 'self'",
    "object-src 'none'",
    "base-uri 'self'",
    "frame-ancestors 'none'",
  ].join('; ');

  const response = NextResponse.next();
  response.headers.set('Content-Security-Policy', csp);
  response.headers.set('x-nonce', nonce); // pass to layout
  return response;
}

export const config = { matcher: '/((?!_next/static|_next/image|favicon.ico).*)' };

Step 2 — Read nonce in root layout

// app/layout.tsx
import { headers } from 'next/headers';

export default function RootLayout({ children }) {
  const nonce = headers().get('x-nonce') || '';

  return (
    <html>
      <head>
        <script
          nonce={nonce}
          dangerouslySetInnerHTML={{
            __html: `window.__NONCE__ = "${nonce}";`
          }}
        />
      </head>
      <body>{children}</body>
    </html>
  );
}

Step 3 — Pass nonce to Script components

// For next/script components
import Script from 'next/script';

<Script
  nonce={nonce}
  src="https://example.com/script.js"
  strategy="afterInteractive"
/>

Third-party scripts with nonces

For Google Analytics, Hotjar, or any third-party script, pass the nonce and add their domains to script-src:

const csp = [
  `script-src 'self' 'nonce-${nonce}' https://www.googletagmanager.com`,
  "connect-src 'self' https://www.google-analytics.com",
].join('; ');

Not sure which resources your page is loading? CSPFixer scans your live URL and generates a CSP based on what it finds.

Scan your page CSP live → CSPFixer