Skip to content
SEO Optimizer is live, Audit on page SEO and AI-search readiness. Try it free
تسجيل الدخول

How to Add Security Headers in Next.js

A fresh Next.js app sends almost no security headers. There is no Content Security Policy, no HSTS, no clickjacking protection, and it advertises itself with an X-Powered-By: Next.js header. Adding Next.js security headers takes two pieces: static headers in next.config, and a Content Security Policy with a per-request nonce in proxy.ts (called middleware.ts before Next.js 16). This guide covers both.

Which Next.js Security Headers You Need

These are the Next.js security headers every production app should send, and where each one belongs:

HeaderWhat it doesWhere to set it in Next.js
Strict-Transport-SecurityForces HTTPS on future visitsnext.config (often set by the host too)
Content-Security-PolicyRestricts where scripts, styles and other resources load from; the main XSS defenseproxy.ts with a nonce, or next.config for a static policy
X-Frame-Options / frame-ancestorsStops other sites framing yours (clickjacking)next.config and CSP
X-Content-Type-OptionsStops MIME-type sniffingnext.config
Referrer-PolicyLimits what URL data leaks to other sitesnext.config
Permissions-PolicyTurns off browser features you do not use (camera, microphone, geolocation)next.config
Cross-Origin-Opener-PolicyIsolates your window from cross-origin popupsnext.config

For what each header protects against in depth, see website security headers explained. For other servers (Nginx, Apache, Cloudflare), see the general security headers guide.

How to Add Next.js Security Headers

1. Add static headers in next.config

Most Next.js security headers are static and belong in config. The headers() function in next.config applies headers to every matching route. Put everything except the CSP here. Setting poweredByHeader: false also removes the X-Powered-By header, which tells attackers your framework for free.

next.config.tsStatic Next.js security headers for every route
import type { NextConfig } from "next";

const securityHeaders = [
  { key: "Strict-Transport-Security", value: "max-age=63072000; includeSubDomains; preload" },
  { key: "X-Frame-Options", value: "SAMEORIGIN" },
  { key: "X-Content-Type-Options", value: "nosniff" },
  { key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
  { key: "Permissions-Policy", value: "camera=(), microphone=(), geolocation=(), browsing-topics=()" },
  { key: "Cross-Origin-Opener-Policy", value: "same-origin" },
];

const nextConfig: NextConfig = {
  poweredByHeader: false,
  async headers() {
    return [{ source: "/:path*", headers: securityHeaders }];
  },
};

export default nextConfig;

Only add preload to HSTS once every subdomain serves HTTPS; preload lists are hard to leave.

2. Add a nonce-based Content Security Policy in proxy.ts

A strict CSP blocks inline scripts, but Next.js injects inline scripts for hydration. The recommended fix is a nonce: a random value generated for each request, added to the CSP and to every script Next.js renders. Next.js reads the nonce from the request's CSP header automatically during server rendering.

proxy.tsGenerate a nonce and set the CSP for each request (middleware.ts before Next.js 16)
import { NextRequest, NextResponse } from "next/server";

export function proxy(request: NextRequest) {
  const nonce = Buffer.from(crypto.randomUUID()).toString("base64");
  const isDev = process.env.NODE_ENV === "development";
  const csp = `
    default-src 'self';
    script-src 'self' 'nonce-${nonce}' 'strict-dynamic'${isDev ? " 'unsafe-eval'" : ""};
    style-src 'self' 'nonce-${nonce}';
    img-src 'self' blob: data:;
    font-src 'self';
    object-src 'none';
    base-uri 'self';
    form-action 'self';
    frame-ancestors 'none';
    upgrade-insecure-requests;
  `.replace(/\s{2,}/g, " ").trim();

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

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

export const config = {
  matcher: [{ source: "/((?!api|_next/static|_next/image|favicon.ico).*)" }],
};

Two things to know. First, nonces require dynamic rendering: statically generated pages are built before any request exists, so they cannot carry a per-request nonce. Second, third-party scripts you load yourself (analytics, tag managers) need the nonce passed to them; read it with (await headers()).get("x-nonce") and pass it to next/script.

3. Use a static CSP if your pages must stay static

If you rely on static generation and cannot switch to dynamic rendering, set a CSP in next.config instead. You will usually need 'unsafe-inline' for scripts, which weakens XSS protection considerably, so prefer hashes or a nonce where you can. Our Content Security Policy guide explains the trade-offs.

4. Roll out the CSP in report-only mode first

A wrong CSP breaks pages. Ship it as Content-Security-Policy-Report-Only for a week, collect violations, fix the sources you actually need (fonts, analytics, embeds), then switch to the enforcing header.

5. Check your host's headers

Vercel, Netlify and Cloudflare may add or override some headers (HSTS in particular). Look at the real response of your deployed site, not just your config, and make sure headers are not set twice with conflicting values.

6. Verify the deployed result

Next.js security headers only count if they reach the browser. Run your production URL through the security headers checker after every change, and check a few different route types (a static page, a dynamic page, an API route) since the matcher may not cover them all.

Common Next.js Security Headers Mistakes

  • A CSP with 'unsafe-inline' and 'unsafe-eval' in production, which gives little protection. 'unsafe-eval' is only needed in development.
  • Forgetting poweredByHeader: false, leaving X-Powered-By: Next.js on every response. See server header information disclosure.
  • A proxy matcher that skips pages, so some routes have no CSP.
  • Allowing framing by omitting X-Frame-Options and frame-ancestors. See the clickjacking guide.

How Scanverra Checks Next.js Security Headers

The Scanverra security headers checker and full security scan fetch your live site and grade HSTS, Content-Security-Policy, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy and the Cross-Origin policies, and flag version-revealing headers such as X-Powered-By and Server. When the site runs on Next.js, the fixes point at next.config and proxy.ts rather than generic server config. The check looks at the response of the URL you scan, so test more than one route if your matcher is selective. The Next.js audit page covers performance and SEO checks for Next.js sites too.

الأسئلة الشائعة

الأسئلة الشائعة

Return an array of headers from the headers() function in next.config for static headers such as HSTS and X-Content-Type-Options, and set a Content Security Policy with a per-request nonce in proxy.ts (middleware.ts before Next.js 16).

Next.js injects inline scripts for hydration, which a strict CSP blocks. Use a nonce generated in proxy.ts; Next.js reads it from the CSP header and applies it to its scripts during server rendering. Pages must be dynamically rendered for this to work.

No. Nonces must be unique per request, and static pages are generated at build time. Use dynamic rendering for nonce support, or a static CSP with hashes for pages that must stay static.

Set poweredByHeader: false in next.config. Next.js then stops sending X-Powered-By: Next.js on responses.

Both do the same job. Next.js 16 renamed Middleware to Proxy, so new projects use proxy.ts with an exported proxy function; older versions use middleware.ts with an exported middleware function.

مجاني - بدون تسجيل

Check this on your own site

Run a free scan and see exactly where you stand - no sign-up required.

تشغيل الفحص المجاني