Next.js CSP Integration
Add CSP headers to your Next.js application using middleware or configuration.
Prerequisites
Section titled “Prerequisites”- Next.js 13+ (App Router or Pages Router)
- Access to middleware.ts or next.config.js
Option 1: Middleware (Recommended)
Section titled “Option 1: Middleware (Recommended)”Create middleware.ts in your project root:
import { NextResponse } from "next/server";import type { NextRequest } from "next/server";
export function middleware(request: NextRequest) { const response = NextResponse.next();
response.headers.set( "Content-Security-Policy-Report-Only", "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; report-uri https://ingest.headerhawk.com/csp/YOUR_SITE_ID" );
return response;}
export const config = { matcher: "/((?!api|_next/static|_next/image|favicon.ico).*)",};Option 2: next.config.js Headers
Section titled “Option 2: next.config.js Headers”For static headers without dynamic content:
/** @type {import('next').NextConfig} */const nextConfig = { async headers() { return [ { source: "/(.*)", headers: [ { key: "Content-Security-Policy-Report-Only", value: "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; report-uri https://ingest.headerhawk.com/csp/YOUR_SITE_ID", }, ], }, ]; },};
module.exports = nextConfig;Dynamic Nonces with Middleware
Section titled “Dynamic Nonces with Middleware”For inline scripts, generate nonces per-request:
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 response = NextResponse.next();
// Set CSP header with nonce response.headers.set( "Content-Security-Policy-Report-Only", `default-src 'self'; script-src 'self' 'nonce-${nonce}'; style-src 'self' 'unsafe-inline'; report-uri https://ingest.headerhawk.com/csp/YOUR_SITE_ID` );
// Pass nonce to components via header response.headers.set("x-nonce", nonce);
return response;}Access the nonce in Server Components:
import { headers } from "next/headers";
export default async function Page() { const headersList = await headers(); const nonce = headersList.get("x-nonce") || "";
return ( <script nonce={nonce} dangerouslySetInnerHTML={{ __html: "..." }} /> );}Per-Route Policies
Section titled “Per-Route Policies”Different policies for different routes:
/** @type {import('next').NextConfig} */const nextConfig = { async headers() { return [ { source: "/admin/:path*", headers: [ { key: "Content-Security-Policy", value: "default-src 'self'; script-src 'self'", }, ], }, { source: "/(.*)", headers: [ { key: "Content-Security-Policy-Report-Only", value: "default-src 'self'; script-src 'self' 'unsafe-inline'; report-uri https://ingest.headerhawk.com/csp/YOUR_SITE_ID", }, ], }, ]; },};Troubleshooting
Section titled “Troubleshooting”Headers Not Applied
Section titled “Headers Not Applied”- Restart the Next.js dev server after config changes
- Check matcher patterns in middleware
- Verify middleware.ts is in the correct location (root or src/)
‘unsafe-eval’ Required
Section titled “‘unsafe-eval’ Required”Some Next.js features require 'unsafe-eval' in development:
const isDev = process.env.NODE_ENV === "development";
const csp = ` default-src 'self'; script-src 'self' ${isDev ? "'unsafe-eval'" : ""}; report-uri https://ingest.headerhawk.com/csp/YOUR_SITE_ID`;Verification
Section titled “Verification”Check headers in browser DevTools (Network tab) or:
curl -I http://localhost:3000 | grep -i content-security-policy