Skip to main content
Back to Blog
Tutorials
2 min read
January 14, 2025

How to Implement Content Security Policy in Next.js

Set up Content Security Policy headers in Next.js with nonces for inline scripts, report-only mode, and violation reporting.

Ryel Banfield

Founder & Lead Developer

CSP prevents XSS attacks by controlling which resources the browser loads. Here is how to set it up in Next.js.

Middleware-Based CSP With Nonces

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

export function middleware(request: NextRequest) {
  // Generate a unique nonce for each request
  const nonce = Buffer.from(crypto.randomUUID()).toString("base64");

  // Build CSP directives
  const cspDirectives = [
    `default-src 'self'`,
    `script-src 'self' 'nonce-${nonce}' 'strict-dynamic'`,
    `style-src 'self' 'nonce-${nonce}'`,
    `img-src 'self' blob: data: https:`,
    `font-src 'self'`,
    `connect-src 'self' https://vitals.vercel-insights.com`,
    `frame-src 'self'`,
    `object-src 'none'`,
    `base-uri 'self'`,
    `form-action 'self'`,
    `frame-ancestors 'none'`,
    `upgrade-insecure-requests`,
  ];

  const cspHeader = cspDirectives.join("; ");

  // Pass nonce to the app via request headers
  const requestHeaders = new Headers(request.headers);
  requestHeaders.set("x-nonce", nonce);
  requestHeaders.set("Content-Security-Policy", cspHeader);

  const response = NextResponse.next({
    request: { headers: requestHeaders },
  });

  // Set CSP on the response
  response.headers.set("Content-Security-Policy", cspHeader);

  return response;
}

export const config = {
  matcher: [
    {
      source: "/((?!api|_next/static|_next/image|favicon.ico).*)",
      missing: [
        { type: "header", key: "next-router-prefetch" },
        { type: "header", key: "purpose", value: "prefetch" },
      ],
    },
  ],
};

Root Layout With Nonce

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

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

  return (
    <html lang="en">
      <body>
        <NonceProvider nonce={nonce}>
          {children}
        </NonceProvider>
      </body>
    </html>
  );
}

Nonce Provider

// components/NonceProvider.tsx
"use client";

import { createContext, useContext } from "react";

const NonceContext = createContext<string>("");

export function NonceProvider({
  nonce,
  children,
}: {
  nonce: string;
  children: React.ReactNode;
}) {
  return <NonceContext.Provider value={nonce}>{children}</NonceContext.Provider>;
}

export function useNonce() {
  return useContext(NonceContext);
}

Script Component With Nonce

"use client";

import Script from "next/script";
import { useNonce } from "./NonceProvider";

export function Analytics() {
  const nonce = useNonce();

  return (
    <Script
      nonce={nonce}
      strategy="afterInteractive"
      dangerouslySetInnerHTML={{
        __html: `
          window.dataLayer = window.dataLayer || [];
          function gtag(){dataLayer.push(arguments);}
          gtag('js', new Date());
          gtag('config', 'G-XXXXXXX');
        `,
      }}
    />
  );
}

CSP Violation Reporting

// app/api/csp-report/route.ts
import { NextResponse } from "next/server";

export async function POST(request: Request) {
  const body = await request.json();
  const report = body["csp-report"] ?? body;

  // Log violation for monitoring
  console.warn("CSP Violation:", {
    blockedUri: report["blocked-uri"],
    violatedDirective: report["violated-directive"],
    documentUri: report["document-uri"],
    sourceFile: report["source-file"],
    lineNumber: report["line-number"],
  });

  // In production, send to your logging service:
  // await logToService("csp-violation", report);

  return NextResponse.json({ received: true });
}

Add the reporting directive to your CSP:

const cspDirectives = [
  // ... existing directives
  `report-uri /api/csp-report`,
  `report-to csp-endpoint`,
];

Report-Only Mode for Testing

Use Content-Security-Policy-Report-Only first to test without blocking:

// Use report-only during development
const headerName =
  process.env.NODE_ENV === "development"
    ? "Content-Security-Policy-Report-Only"
    : "Content-Security-Policy";

response.headers.set(headerName, cspHeader);

Additional Security Headers

// Add alongside CSP in middleware
response.headers.set(
  "Strict-Transport-Security",
  "max-age=63072000; includeSubDomains; preload",
);
response.headers.set("X-Content-Type-Options", "nosniff");
response.headers.set("X-Frame-Options", "DENY");
response.headers.set("X-XSS-Protection", "0"); // Rely on CSP instead
response.headers.set("Referrer-Policy", "strict-origin-when-cross-origin");
response.headers.set(
  "Permissions-Policy",
  "camera=(), microphone=(), geolocation=()",
);

next.config.ts Alternative

For simpler CSP without nonces:

// next.config.ts
import type { NextConfig } from "next";

const config: NextConfig = {
  async headers() {
    return [
      {
        source: "/(.*)",
        headers: [
          {
            key: "Content-Security-Policy",
            value: [
              "default-src 'self'",
              "script-src 'self'",
              "style-src 'self' 'unsafe-inline'",
              "img-src 'self' blob: data:",
              "font-src 'self'",
              "object-src 'none'",
              "frame-ancestors 'none'",
            ].join("; "),
          },
        ],
      },
    ];
  },
};

export default config;

Need Security Hardening?

We implement comprehensive security for web applications. Contact us to protect your site.

CSPsecurityheadersNext.jsXSStutorial

Ready to Start Your Project?

RCB Software builds world-class websites and applications for businesses worldwide.

Get in Touch

Related Articles