July 18, 2026HeaderTest Team386 views

How to Add a Content Security Policy to a Next.js App

Next.js emits inline hydration scripts, so a naive script-src blocks your own app. Here is the clean nonce + strict-dynamic setup in middleware, plus the styled-jsx and next/image gotchas.

Why Next.js needs a hand with CSP

A strong Content Security Policy is the single most effective defense against cross-site scripting. But Next.js emits inline bootstrap scripts for hydration, so a naive script-src 'self' policy blocks your own app. The clean fix is a per-request nonce combined with strict-dynamic.

Generate a nonce in middleware

Create middleware.ts at the project root. It generates a random nonce per request, puts it in the CSP header, and forwards it to the app via a request header:

import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export function middleware(request: NextRequest) {
  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:",
    "object-src 'none'",
    "base-uri 'self'",
    "frame-ancestors 'none'",
  ].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;
}

Use the nonce in your layout

Read the nonce from headers in your root layout. Next automatically applies it to its own framework scripts once it sees a nonce in the CSP:

import { headers } from 'next/headers';

export default async function RootLayout({ children }) {
  const nonce = (await headers()).get('x-nonce') ?? '';
  return (
    <html>
      <body>{children}</body>
    </html>
  );
}

Why strict-dynamic

strict-dynamic tells the browser to trust scripts loaded by an already-trusted (nonced) script, so you do not have to allow-list every CDN. It also makes host-based sources redundant, shrinking your policy and closing CDN-bypass holes.

Common gotchas

  • styled-jsx and inline styles need style-src 'unsafe-inline' (or a style nonce). Style injection is far lower risk than script, so this is an accepted trade-off.
  • Third-party scripts (analytics, tag managers) load fine under strict-dynamic as long as your own nonced loader injects them.
  • next/image needs img-src to include remote image hosts and data: for blur placeholders.

Do not forget the other headers

CSP is the headline, but pair it with HSTS, X-Content-Type-Options, X-Frame-Options, and Referrer-Policy. The static ones can go in next.config.js under headers().

Verify it

Deploy, then scan your site with HeaderTest to confirm the CSP is present, nonce-based, and enforcing (not report-only). Re-scan after each change to watch your grade climb.

Topics

cspcontent security policynextjsnext.jsnoncestrict-dynamicsecurity headers

Check Your Website's Security

Use our free scanner to analyze your CSP and security headers.

Scan Now - Free