Go-Live Clearance

Next.js · security headers · hardening

Missing Security Headers in Next.js

Vercel and Next.js do not add security headers by default. Your app ships with an F on securityheaders.com and you only find out when a security audit or a paying customer asks. Paste your URL — we check HSTS, CSP, X-Frame-Options, and three more, then stamp CLEARED / HOLD / DENIED.

Headers Next.js apps typically miss

These are absent from a default Next.js + Vercel deployment.

  • No Strict-Transport-Security (HSTS)

    Browsers may still use HTTP on revisits. Critical for apps with login or payment — session cookies leak on the HTTP hop.

  • No X-Frame-Options / frame-ancestors

    Your app can be embedded in an iframe on a malicious site. Clickjacking targets auth flows and payment buttons.

  • No X-Content-Type-Options: nosniff

    Browsers guess MIME types on user-uploaded files. A mismatch opens XSS vectors on CDN edges.

  • No Content-Security-Policy

    Third-party scripts (analytics, chat widgets) can load anything. CSP is the header auditors ask for first.

  • No Referrer-Policy / Permissions-Policy

    Full URLs leak to third-party origins. Camera, microphone, and geolocation defaults stay wide open.

next.config.js — all headers at once

One config block covers every header we check. Copy and adjust.

next.config.js — complete headers

/** @type {import('next').NextConfig} */
const nextConfig = {
  async headers() {
    return [
      {
        source: '/(.*)',
        headers: [
          {
            key: 'Strict-Transport-Security',
            value: 'max-age=31536000; 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=()',
          },
        ],
      },
    ]
  },
}

module.exports = nextConfig

CSP starter — tighten after measuring

// Add to the headers array above:
{
  key: 'Content-Security-Policy',
  value: "default-src 'self'; img-src 'self' data: https:; script-src 'self'; style-src 'self' 'unsafe-inline'",
}

// ⚠️ CSP breaks things if too strict.
// 1. Deploy with Content-Security-Policy-Report-Only first
// 2. Check browser console for violation reports
// 3. Tighten, then switch to the real header

Still check by hand

  • Deploy and re-scan — headers appear only in production, not in next dev
  • If using a CDN (Cloudflare, etc.), set headers at the edge to avoid duplicates
  • Start with Content-Security-Policy-Report-Only before enforcing a real CSP
  • Test iframe embedding after adding X-Frame-Options: DENY — break intentional embeds?

Related