Guide · Next.js

Security headers in Next.js.

Short answer: Add an async headers() function to next.config.js that sends six headers on every route: Content-Security-Policy, Strict-Transport-Security, X-Content-Type-Options: nosniff, X-Frame-Options (or CSP frame-ancestors), Referrer-Policy and Permissions-Policy. Also set poweredByHeader: false. Start the content policy in report-only mode so it can't break your app, then tighten it.

Updated 25 October 2026

What to do, step by step

Headers are instructions your server sends with every page. Browsers use them to refuse framing by other sites, to refuse guessing file types, to stay on the secure connection, and to limit where scripts may come from.

  1. Add the headers to next.config.js

    // next.config.js
    const securityHeaders = [
      { key: 'Strict-Transport-Security', value: 'max-age=63072000; includeSubDomains; preload' },
      { key: 'X-Content-Type-Options', value: 'nosniff' },
      { key: 'X-Frame-Options', value: 'DENY' },
      { key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
      { key: 'Permissions-Policy', value: 'camera=(), microphone=(), geolocation=()' },
      { key: 'Content-Security-Policy-Report-Only',
        value: "default-src 'self'; img-src 'self' data: https:; frame-ancestors 'none'" },
    ]
    
    module.exports = {
      poweredByHeader: false,
      async headers() {
        return [{ source: '/:path*', headers: securityHeaders }]
      },
    }

    Only add preload to HSTS once every subdomain works over the secure connection: it is hard to undo.

  2. Make the content policy fit your app

    Deploy with Content-Security-Policy-Report-Only first, open the app, and read the browser console. Each violation names a source to allow (your analytics, Stripe's script, your Supabase address in connect-src). When the console is quiet, rename the header to Content-Security-Policy. For a strict policy with nonces, Next.js documents a middleware pattern; note it makes pages render dynamically.

  3. Check they arrive

    curl -sI https://your-site.com | grep -iE 'strict|content-security|x-frame|x-content|referrer|permissions'

    Static exports (output: 'export') ignore headers(); set them on your host instead (for example vercel.json, netlify.toml or your web server).

  4. Keep Next.js itself up to date

    Headers do not help against a hole in the framework. In March 2025 a flaw let attackers skip Next.js middleware by sending one header (CVE-2025-29927), and in December 2025 a critical flaw in React Server Components (CVE-2025-55182) affected Next.js App Router apps. Stay on the latest patch release of your major version.

What vibeship checks for you

You can do all of the above by hand. If you'd rather start with a list of what's wrong, the vibeship quick check runs from outside, the way a stranger sees your site. The quick check is free and needs no account. It shows your grade and how many problems sit in each area; what each problem is and how to fix it is in the full report.

Checked automatically

  • Whether each of the six security headers is present on your homepage
  • Whether everything is served over the secure connection, without falling back
  • Whether your server announces its software and version (such as X-Powered-By)
  • Leftover developer code in the scripts your site ships

Deeper, and what we don't check

  • The quick check confirms a header exists; it does not grade how strict your content policy is
  • The extensive scan checks pages beyond the homepage and tries real attacks a weak policy would let through
  • Not checked: which Next.js version you run; with a subscription, the code audit reads your dependencies

Questions people ask

Should I still set X-XSS-Protection?

No. Modern browsers ignore it and the old filter caused problems of its own. Leave it out, or set it to 0, and rely on a content policy instead.

Does Vercel set security headers for me?

Hosts differ and change their defaults, so don't assume. Run the curl command above against your live domain and add whatever is missing in next.config.js.

X-Frame-Options or frame-ancestors?

frame-ancestors in the content policy is the modern control and wins where both are set. Sending both is harmless and covers older browsers.

Will a content policy break my app?

It can, which is why you start with the report-only header. Third-party scripts, inline styles and your API's address are the usual things to allow.

Best effort, not a guarantee: Our scans are best effort: they can miss a weakness or flag one that isn't there. This is advice, not a guarantee, and it doesn't replace a security firm. Your site's safety stays your responsibility. Read the terms.

Related guides