Back to guides

Next.js CSP Configuration

By Davy Rogers

Content Security Policy tells the browser which resources are allowed to load and execute. Without one, any XSS vulnerability can load scripts from anywhere.

Why Next.js Needs Special Handling

Next.js injects inline scripts for hydration, page data, and route transitions. A naive CSP that blocks all inline scripts will break your app. The solution is nonce-based CSP.

Reference: Next.js - Content Security Policy

Nonce-Based CSP with Middleware

Generate a unique nonce per request in middleware. In App Router, Next.js extracts the nonce from the request CSP header during server-side rendering and applies it to Next.js-managed scripts and styles, but this only works for dynamically rendered pages and when the CSP header includes a nonce-... token.

// middleware.ts
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";

export function middleware(request: NextRequest) {
  const nonce = btoa(crypto.randomUUID());

  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'`,
    `connect-src 'self'`,
    `frame-ancestors 'none'`,
    `base-uri 'self'`,
    `form-action 'self'`,
    `upgrade-insecure-requests`,
  ].join("; ");

  const requestHeaders = new Headers(request.headers);
  requestHeaders.set("x-nonce", nonce);

  const response = NextResponse.next({
    request: { headers: requestHeaders },
  });
  response.headers.set("Content-Security-Policy", csp);

  return response;
}

Reading the Nonce in Server Components

Access the nonce via the request headers and pass it to components that need it:

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

export default async function RootLayout({ children }: { children: React.ReactNode }) {
  const headersList = await headers();
  const nonce = headersList.get("x-nonce") ?? "";

  return (
    <html lang="en">
      <body>
        {children}
        <script nonce={nonce} src="/analytics.js" />
      </body>
    </html>
  );
}

Directive Breakdown

DirectiveValuePurpose
default-src'self'Baseline: only allow resources from your origin
script-src'self' 'nonce-...' 'strict-dynamic'Allow own scripts + nonced inline scripts; strict-dynamic allows scripts loaded by trusted scripts
style-src'self' 'unsafe-inline'Next.js and CSS-in-JS often require inline styles
img-src'self' data: https:Allow images from your origin, data URIs, and HTTPS sources
font-src'self'Only load fonts from your origin
connect-src'self'Restrict fetch/XHR/WebSocket to your origin
frame-ancestors'none'Prevent your site from being embedded in iframes (clickjacking protection)
base-uri'self'Prevent <base> tag injection
form-action'self'Restrict form submission targets

Reference: MDN - Content-Security-Policy

Common Adjustments

Third-Party Scripts (Analytics, Ads)

Add the domain to script-src and connect-src:

script-src 'self' 'nonce-...' 'strict-dynamic' https://www.googletagmanager.com;
connect-src 'self' https://www.google-analytics.com;

External Images (CDN, User Avatars)

Add the CDN domain to img-src:

img-src 'self' data: https://cdn.example.com;

WebSocket Connections

Add wss: to connect-src:

connect-src 'self' wss://your-websocket-domain.com;

Report-Only Mode

Start with Content-Security-Policy-Report-Only to collect violations without breaking anything:

response.headers.set("Content-Security-Policy-Report-Only", csp);

Add a reporting endpoint:

report-uri /api/csp-report;
report-to default;

Monitor violations, fix legitimate sources, then switch to enforcing mode.

Reference: MDN - Content-Security-Policy-Report-Only

Testing CSP

  • Open browser DevTools → Console. CSP violations appear as errors.
  • Use Google CSP Evaluator to check your policy.
  • Run Lighthouse - it flags missing or weak CSP.

Common Mistakes

  • Using unsafe-inline in script-src: defeats the purpose of CSP. Use nonces instead.
  • Using unsafe-eval: allows eval() which attackers exploit. Avoid unless a dependency absolutely requires it.
  • Overly permissive connect-src: connect-src * lets XSS exfiltrate data anywhere.
  • Forgetting frame-ancestors: without it, your site can be framed for clickjacking.
  • Not testing after deployment: CSP can behave differently in production due to CDN or proxy headers.

Related Guides

Published 04 Mar 2026

Frequently asked questions

Whats the safest baseline CSP for a Next.js app?
default-src self with explicit allowlists for script, style, image, font, and connect sources, plus a per-request nonce on the script tag. Avoid unsafe-inline and unsafe-eval where you can.
How do I add nonces to inline scripts in Next.js?
Generate a nonce in middleware.ts, attach it to the request, and pass it through to the Script component or your layout. Next.js 14+ has first-class nonce propagation.
Will CSP break Vercel Analytics or third-party scripts?
It will until you allowlist them. Whitelist by exact host (vitals.vercel-insights.com, etc.) rather than opening up wildcards, and review the list at every major dependency change.
Should I report violations?
Yes - set report-to or report-uri to a sink you actually monitor. CSP violations are the cheapest XSS detection signal you will ever get.

Related

Want a professional to look at it?Get an AppSec Health Check.