JustAppSec
Back to guides

Next.js CSP Configuration

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


Content is AI-assisted and reviewed by our team, but issues may be missed and best practices evolve rapidly, send corrections to [email protected]. Always consult official documentation and validate key implementation decisions before making design or security choices.

Need help?Get in touch.