SecHead
Website scannenKontakt
Platform Guide14 min read

Security Headers Checklist for Next.js and Vercel

How to set all security headers in a Next.js application deployed on Vercel - using next.config.js, middleware, or vercel.json.

SL
Seven Labs · 24 February 2026
2,898 words

Next.js makes it straightforward to set security headers, but the right method depends on whether you need dynamic headers (different per route or per request) or static headers (the same for everything). If you are deploying your Next.js application on Vercel, you have multiple layers where you can enforce these headers: application-level (next.config.js), edge-level (middleware.ts), or platform-level (vercel.json). This comprehensive guide covers all three methods, explaining the nuances, best practices, and security considerations for each.

Scan your Next.js site → to see your current security header grade before making changes.


Quick Answer: Setting Next.js Security Headers

If you want the fastest way to secure your Next.js application, the recommended approach for most developers is to use the headers() function within your next.config.js file. This applies headers globally to all routes and API endpoints.

Step 1: Open your next.config.js file. Step 2: Add an async headers() function that returns an array of route-matching rules and their associated headers. Step 3: Include essential headers like Strict-Transport-Security, X-Frame-Options, X-Content-Type-Options, and Content-Security-Policy.

Here is the baseline configuration you should implement:

// next.config.js
const securityHeaders = [
  { key: 'X-DNS-Prefetch-Control', value: 'on' },
  { key: 'Strict-Transport-Security', value: 'max-age=31536000; includeSubDomains; preload' },
  { key: 'X-Frame-Options', value: 'DENY' },
  { key: 'X-Content-Type-Options', value: 'nosniff' },
  { key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
  { key: 'Permissions-Policy', value: 'camera=(), microphone=(), geolocation=()' }
];

module.exports = {
  async headers() {
    return [
      {
        source: '/(.*)',
        headers: securityHeaders,
      },
    ];
  },
};

Deploying this will instantly upgrade your security posture and defend against clickjacking, MIME-sniffing, and downgrade attacks.


Why Next.js Security Headers Matter on Vercel

Modern web applications built with React and Next.js are incredibly powerful, but they are not immune to client-side attacks like Cross-Site Scripting (XSS), Clickjacking, or Man-in-the-Middle (MitM) attacks. Next.js Security Headers are HTTP response headers that instruct the browser on how to behave when handling your site's content.

When you deploy a Next.js app to Vercel, the architecture consists of:

  1. The Vercel Edge Network: A global CDN that caches static assets and routes requests.
  2. Edge Middleware: Functions that run at the network edge before a request hits the origin.
  3. Serverless Functions: Node.js environments that render SSR (Server-Side Rendering) pages and API routes.

Because of this distributed architecture, where you define your headers dictates when and how they are applied. If you define them in vercel.json, they are applied by the routing layer. If you use Middleware, they are generated at the Edge. If you use next.config.js, they are applied during the Next.js routing lifecycle.

Let's explore the three core methods for deploying Vercel Security headers.


The next.config.js file is the standard, most idiomatic way to apply static HTTP response headers in Next.js. By utilizing the async headers() function, you can map specific headers to regex-based path patterns.

How it Works

When Next.js builds your application, it compiles the routing manifesto. Any headers defined in next.config.js are baked into the Next.js routing layer. When a request matches the source pattern (e.g., /(.*) for all routes), the Node.js serverless function or static file server appends these headers to the HTTP response.

Implementation Guide

// next.config.js
/** @type {import('next').NextConfig} */

// Define your Content Security Policy (CSP)
const cspHeader = `
  default-src 'self';
  script-src 'self' 'unsafe-eval' 'unsafe-inline';
  style-src 'self' 'unsafe-inline';
  img-src 'self' blob: data:;
  font-src 'self';
  object-src 'none';
  base-uri 'self';
  form-action 'self';
  frame-ancestors 'none';
  upgrade-insecure-requests;
`

const securityHeaders = [
  {
    key: 'Content-Security-Policy',
    value: cspHeader.replace(/\n/g, ''),
  },
  {
    key: 'X-DNS-Prefetch-Control',
    value: 'on',
  },
  {
    key: 'Strict-Transport-Security',
    value: 'max-age=31536000; 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=()',
  }
];

module.exports = {
  async headers() {
    return [
      {
        source: '/(.*)',
        headers: securityHeaders,
      },
    ];
  },
};

Understanding React CSP Implications

Notice the use of 'unsafe-inline' in the style-src directive. In a perfect world, we would remove this. However, Next.js often relies on inline styles during the SSR process, and popular CSS-in-JS libraries (like styled-components, Emotion, or Tailwind's dynamic classes) inject <style> tags directly into the DOM.

Similarly, during local development, Next.js requires 'unsafe-eval' for Fast Refresh (Hot Module Replacement) to function. If you enforce a strict CSP without 'unsafe-eval' in development, your Next.js app will crash.

Pro Tip: You can conditionally apply headers based on the environment:

const isDev = process.env.NODE_ENV !== 'production';

const cspHeader = `
  default-src 'self';
  script-src 'self' ${isDev ? "'unsafe-eval'" : ""};
  style-src 'self' 'unsafe-inline';
`;

Method 2: Edge Middleware for Dynamic Nonce-based CSP

Static headers in next.config.js are great, but they fall short if you want a truly airtight React CSP. A strict CSP uses a "nonce" (number used once)-a cryptographically secure random string generated uniquely for every single HTTP request.

Because next.config.js generates static routing rules, it cannot generate a unique nonce per request. To achieve dynamic headers, we must use Next.js Edge Middleware.

The Architecture of Nonce-based Headers

  1. A user requests a page from your Vercel deployment.
  2. The request hits Vercel's Edge Network and triggers middleware.ts.
  3. The Middleware generates a random Base64 nonce.
  4. The Middleware constructs a CSP header using this nonce and attaches it to the HTTP response.
  5. The Middleware also attaches the nonce to the request headers so that your Next.js Server Components can read it and apply it to inline <script> tags.

Implementation Guide

First, create middleware.ts in your project root (or src/ directory):

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

export function middleware(request: NextRequest) {
  // Generate a random nonce using the Web Crypto API (supported at Edge)
  const nonce = Buffer.from(crypto.randomUUID()).toString('base64');

  // Define the strict CSP
  const cspHeader = `
    default-src 'self';
    script-src 'self' 'nonce-${nonce}' 'strict-dynamic';
    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';
  `.replace(/\s{2,}/g, ' ').trim();

  // Clone the request headers and set the nonce so Server Components can read it
  const requestHeaders = new Headers(request.headers);
  requestHeaders.set('x-nonce', nonce);

  // Create the response, passing the modified request headers forward
  const response = NextResponse.next({
    request: {
      headers: requestHeaders,
    },
  });

  // Set the CSP header on the outgoing response
  response.headers.set('Content-Security-Policy', cspHeader);
  
  // Set other security headers
  response.headers.set('X-Content-Type-Options', 'nosniff');
  response.headers.set('X-Frame-Options', 'DENY');
  response.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin');

  return response;
}

export const config = {
  // Match all request paths except for Next.js internal files and static assets
  matcher: [
    '/((?!api|_next/static|_next/image|favicon.ico).*)',
  ],
};

Applying the Nonce in Next.js App Router

Now that Middleware is injecting the CSP and passing the nonce in the request headers, you must apply it to your Next.js Document or Root Layout.

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

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  const nonce = headers().get('x-nonce') || '';

  return (
    <html lang="en">
      <head>
        {/* Next.js automatically applies the nonce to its internal scripts if passed here */}
      </head>
      <body>
        {children}
        
        {/* Example of adding a custom script securely */}
        <script nonce={nonce} dangerouslySetInnerHTML={{ __html: `console.log('Secure execution!');` }} />
      </body>
    </html>
  );
}
$ curl -I https://strict-csp.vercel.app
HTTP/2 200
content-type: text/html; charset=utf-8
x-middleware-cache: no-cache
content-security-policy: default-src 'self'; script-src 'self' 'nonce-ZTYxZDk5YzYtYjhkNC...' 'strict-dynamic'; style-src 'self' 'nonce-ZTYxZDk5YzYtYjhkNC...';
x-content-type-options: nosniff
x-frame-options: DENY

Using this Edge Middleware approach ensures your Next.js application has maximum protection against XSS vulnerabilities.


Method 3: Platform-Level vercel.json Configuration

If you are using next export to build a purely Static Site Generation (SSG) app, or if you simply prefer defining configuration outside of the Node.js runtime, you can use vercel.json.

This tells the Vercel routing engine directly what headers to append to assets. Because this happens at the Edge CDN layer, it is incredibly fast and completely bypasses your Next.js server logic.

Implementation Guide

Create a vercel.json file in the root of your repository:

{
  "headers": [
    {
      "source": "/(.*)",
      "headers": [
        {
          "key": "Strict-Transport-Security",
          "value": "max-age=31536000; includeSubDomains; preload"
        },
        {
          "key": "X-Frame-Options",
          "value": "DENY"
        },
        {
          "key": "X-Content-Type-Options",
          "value": "nosniff"
        },
        {
          "key": "Referrer-Policy",
          "value": "strict-origin-when-cross-origin"
        },
        {
          "key": "Permissions-Policy",
          "value": "camera=(), microphone=(), geolocation=(), usb=()"
        }
      ]
    },
    {
      "source": "/_next/static/(.*)",
      "headers": [
        {
          "key": "Cache-Control",
          "value": "public, max-age=31536000, immutable"
        }
      ]
    }
  ]
}

Vercel Security Hierarchy

Warning: Be mindful of the hierarchy. If you define headers in BOTH next.config.js and vercel.json, Vercel processes vercel.json first at the routing layer, and then the Next.js application runs. Next.js headers will typically override vercel.json headers for dynamic routes. It is best practice to choose one method for static headers to avoid configuration drift and debugging nightmares.


Deep Dive: The Essential Security Headers Checklist

To ensure a robust Vercel Security posture, here is the breakdown of the exact headers you should be using and why.

1. Strict-Transport-Security (HSTS)

HSTS ensures that browsers only connect to your Next.js app over HTTPS, completely eliminating downgrade attacks.

  • Optimal Value: max-age=31536000; includeSubDomains; preload
  • Why it matters: Vercel provisions SSL certificates by default. HSTS guarantees that users never accidentally access your site via port 80 (HTTP). By adding preload, you can submit your domain to the HSTS preload list, meaning browsers will enforce HTTPS even on the very first visit.
https://sechead-demo.vercel.app
đź”’ Connection is secure
Your connection to this site is encrypted and authenticated.

2. X-Frame-Options

Prevents your website from being embedded within an iframe on another domain, preventing Clickjacking attacks.

  • Optimal Value: DENY or SAMEORIGIN
  • Why it matters: If an attacker frames your Next.js dashboard, they can overlay invisible buttons on top of your UI, tricking users into performing actions they didn't intend. While frame-ancestors in CSP supersedes this header in modern browsers, X-Frame-Options is required for legacy support.

3. X-Content-Type-Options

Stops browsers from attempting to "sniff" the MIME type of a file and forces them to respect the Content-Type header.

  • Optimal Value: nosniff
  • Why it matters: If a user uploads an image to your Next.js app that actually contains malicious JavaScript, a browser without nosniff might try to execute it as a script. This header forces the browser to treat it strictly as an image.

4. Referrer-Policy

Controls how much referring domain information is passed along when a user navigates away from your site.

  • Optimal Value: strict-origin-when-cross-origin
  • Why it matters: If your Next.js app has sensitive URLs (e.g., password reset tokens in the query string), clicking an external link could leak that URL in the Referer header to a third party. This setting ensures that cross-origin requests only see the root domain, not the full path.

5. Permissions-Policy (Formerly Feature-Policy)

Restricts which browser APIs (like camera, microphone, geolocation) the application and its embedded iframes can use.

  • Optimal Value: camera=(), microphone=(), geolocation=(), payment=(), usb=()
  • Why it matters: Even if an attacker finds an XSS vulnerability in your app, they cannot secretly turn on the user's webcam or track their location if you have locked down the Permissions-Policy.

Handling Third-Party Scripts in Next.js

When implementing a strict CSP, third-party scripts (Google Analytics, Stripe, HubSpot) often break. Next.js provides the <Script> component from next/script to optimize third-party loading, but you must still configure your CSP to allow them.

If you are using Google Analytics, your CSP script-src and connect-src must be updated:

const cspHeader = `
  default-src 'self';
  script-src 'self' 'unsafe-inline' https://www.googletagmanager.com;
  connect-src 'self' https://www.google-analytics.com https://stats.g.doubleclick.net;
  img-src 'self' data: https://www.google-analytics.com;
`
🚨 CSP Violation Report
Blocked-URI: https://js.stripe.com/v3/
Violated-Directive: script-src 'self'
Original-Policy: default-src 'self'; script-src 'self'

Fix: Add https://js.stripe.com to your script-src directive in next.config.js.

To gracefully debug CSP issues without breaking your production site, use the Content-Security-Policy-Report-Only header instead of the enforcing header. This will print violations to the browser console (or send them to a reporting endpoint) without actually blocking the resources.


Troubleshooting Vercel Deployments

After deploying your updated next.config.js or middleware.ts, verify your site using browser developer tools or terminal commands.

Common Issues

  1. Headers missing on static assets: If you use next.config.js, headers apply to routes handled by the Next.js server. Static assets inside public/ or _next/static/ might not receive the same headers depending on how Vercel caches them. Use vercel.json to guarantee coverage across all edge nodes.
  2. Infinite Redirects with HSTS: Ensure that your local development server is exempt from HSTS, otherwise localhost will be forced to use HTTPS, breaking your dev environment.
  3. Middleware Cache issues: If your nonce-based CSP isn't updating per request, ensure you are not caching the HTML response aggressively at the CDN layer. Middleware modifying responses requires the response to be dynamic.

People Also Ask (FAQ)

1. Where do I put security headers in Next.js?

You can put security headers in three places: the headers() async function in next.config.js (for application-level routing), inside middleware.ts (for dynamic edge injection), or in vercel.json (for Vercel's edge network routing).

2. Can I use next.config.js headers for static exports?

No. If you use next export or output: 'export' to build a purely static HTML/CSS/JS site, Next.js routing is disabled. You must configure your security headers on your hosting provider, such as using vercel.json for Vercel or _headers for Netlify.

3. Why is my Next.js App Router crashing with a strict CSP?

The Next.js App Router relies heavily on inline scripts and styles to bootstrap React Server Components. If you remove 'unsafe-inline' without implementing a proper Nonce architecture via Middleware, React hydration will fail.

4. What is a Nonce in React CSP?

A Nonce (Number Used Once) is a randomly generated, single-use token embedded in your CSP header and appended as a nonce attribute to valid <script> and <style> tags. The browser only executes scripts whose nonce matches the one provided in the header.

5. Does Vercel automatically add security headers?

Vercel automatically provisions SSL certificates and handles basic edge security, but it does NOT automatically inject strict application security headers like Content-Security-Policy or X-Frame-Options. You must configure these manually.

6. Should I use X-XSS-Protection?

No. The X-XSS-Protection header is deprecated and ignored by modern browsers (Chrome, Edge, Firefox). In some legacy browsers, it actually introduces vulnerabilities. Rely on a strong Content-Security-Policy instead.

7. How do I test my Next.js security headers locally?

You can test headers locally by running npm run build and npm run start to spin up a production-like environment. Then, use curl or inspect the network tab in your browser's dev tools.

8. What is the difference between next.config.js and vercel.json?

next.config.js is tied to the Next.js framework runtime and works regardless of where you deploy (Vercel, AWS, Docker). vercel.json is proprietary to Vercel's Edge Network infrastructure.

9. How do I fix "Blocked-URI" CSP errors for Google Fonts?

Google Fonts requires both the stylesheet and the font files. You need to allow the CSS domain in style-src and the font domain in font-src: style-src 'self' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com;

10. How do I enforce HTTPS on Vercel?

Vercel enforces HTTPS by default on all .vercel.app domains and custom domains. However, to instruct the browser to never attempt an HTTP connection in the future, you must set the Strict-Transport-Security header.

11. Can Middleware run on static pages?

Yes, Next.js Edge Middleware intercepts the request before it hits the static file cache. This means you can inject dynamic headers (like a CSP nonce) even if the underlying HTML page was statically generated.

12. Does Next.js support Permissions-Policy natively?

Next.js supports Permissions-Policy just like any other string-based HTTP header. You simply define it in your next.config.js headers array.

13. What happens if X-Frame-Options and CSP frame-ancestors conflict?

Modern browsers prioritize the frame-ancestors directive in the Content-Security-Policy. If frame-ancestors is present, X-Frame-Options is completely ignored. However, you should include both for backward compatibility.

14. Why do Emotion and Styled-Components break with strict CSP?

CSS-in-JS libraries dynamically inject <style> tags into the DOM at runtime. If your CSP lacks 'unsafe-inline' for style-src, the browser blocks these injections. To fix this, you must configure the libraries to consume a nonce generated by your server.

15. How do I use Report-Only for Next.js CSP?

To test a CSP without breaking the site, use the header key Content-Security-Policy-Report-Only instead of Content-Security-Policy. You can append a report-uri directive to send violation logs to an endpoint for analysis.


Conclusion

Securing your Next.js application on Vercel is a multi-layered process. By understanding the distinct roles of next.config.js, Edge Middleware, and vercel.json, you can deploy robust defenses against modern web vulnerabilities. Start with a solid baseline in your Next.js config, and progressively move towards a strict, nonce-based CSP as your application matures.

Scan your site → to continuously monitor your security header posture and ensure your deployments remain locked down.


SEO Metadata Meta Title: Next.js Security Headers: The Ultimate Vercel Configuration Guide Meta Description: Master Next.js security headers. Learn how to configure next.config.js, Middleware, and vercel.json for robust React CSP and Vercel edge security. URL Slug: security-headers-nextjs-vercel Keywords: Next.js Security Headers, next.config.js headers, Vercel Security, React CSP, Content Security Policy, HSTS, Middleware security.


Continue your journey into web security with these related, deep-dive articles from the SecHead team:

Related articles

Free tool

Check your own security headers

Instant grade, plain-language explanations, and a full remediation plan - no signup needed.

Scan your site now →