Security headers in Next.js — HSTS, CSP, frame-ancestors, complete config
Vercel doesn't set security headers by default. HSTS, CSP, X-Frame-Options, Permissions-Policy all need explicit config. Here's the canonical next.config.mjs.
Default Next.js + Vercel = no security headers. Add HSTS + CSP + frame-ancestors + permissions-policy via next.config.mjs.
What it is
Security headers are HTTP response headers that instruct browsers to enforce protections (HTTPS-only, no framing, no Flash, etc.). Without them, browser defaults are permissive.
Vulnerable example
// next.config.mjs (default — no security headers)
const nextConfig = { reactStrictMode: true };
export default nextConfig;Fixed example
// next.config.mjs
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: "accelerometer=(), camera=(), geolocation=(), microphone=()" },
{ key: "Cross-Origin-Opener-Policy", value: "same-origin" },
];
const nextConfig = {
reactStrictMode: true,
poweredByHeader: false,
async headers() { return [{ source: "/:path*", headers: securityHeaders }]; },
};
export default nextConfig;How Securie catches it
apps/web/app/api/route.ts:22Security headers in Next.js
Securie's static-rules pre-filter detects missing security headers in next.config.mjs at PR time; flags as a high-severity finding for shipped-without-headers PRs.
// next.config.mjs
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: "accelerometer=(), camera=(), geolocation=(), microphone=()" },
{ key: "Cross-Origin-Opener-Policy", value: "same-origin" },
];
const nextConfig = {
reactStrictMode: true,
poweredByHeader: false,
async headers() { return [{ source: "/:path*", headers: securityHeaders }]; },
};
export default nextConfig;Checklist
- HSTS with max-age >= 1 year + preload
- X-Frame-Options DENY (matches CSP frame-ancestors 'none')
- X-Content-Type-Options nosniff
- Referrer-Policy strict-origin-when-cross-origin
- Permissions-Policy lock down accelerometer/camera/geo/etc
- CSP with nonce via middleware (advanced)
FAQ
What about CSP?
CSP requires nonce-wiring through middleware; see Securie's apps/web/middleware.ts for the canonical pattern.
Should I submit to HSTS preload list?
Yes — at hstspreload.org, after testing.
Related guides
CORS is one of the most misunderstood security headers. Here is exactly when `*` is safe, when it is catastrophic, and how to configure CORS correctly for a Next.js + Supabase stack.
Server Actions are Next.js's RPC mechanism — async functions marked "use server" that run on the server but are called from client components. The convenience hides the risk: every Server Action is an unauthenticated public API endpoint by default. Here is the vulnerable pattern, the fix, and the audit checklist.
NEXT_PUBLIC_-prefixed env vars ship in the client bundle. Server secrets accidentally prefixed = bundled credentials shipped to every visitor. Here's the detection + fix.
Server Actions accept cross-origin POSTs by default. Add origin check or token validation.