Why Security Headers Are Your Fastest Win
Most security improvements take weeks to implement.
HTTP security headers take 20 minutes and cover a significant attack surface.
The Headers That Matter
// next.config.js
const securityHeaders = [
// Prevent clickjacking
{ key: 'X-Frame-Options', value: 'DENY' },
// Block MIME type sniffing
{ key: 'X-Content-Type-Options', value: 'nosniff' },
// Referrer policy (don't leak URLs)
{ key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
// Force HTTPS (HSTS)
{ key: 'Strict-Transport-Security', value: 'max-age=63072000; includeSubDomains; preload' },
// Permissions policy (disable unnecessary browser features)
{ key: 'Permissions-Policy', value: 'camera=(), microphone=(), geolocation=()' },
// Content Security Policy
{
key: 'Content-Security-Policy',
value: [
"default-src 'self'",
"script-src 'self' 'unsafe-eval' 'unsafe-inline'", // unsafe-* needed for Next.js dev
"style-src 'self' 'unsafe-inline'",
"img-src 'self' blob: data: https:",
"font-src 'self'",
"object-src 'none'",
"base-uri 'self'",
"form-action 'self'",
"frame-ancestors 'none'",
"upgrade-insecure-requests",
].join('; ')
}
]
module.exports = {
async headers() {
return [
{
source: '/(.*)',
headers: securityHeaders
}
]
}
}
Content Security Policy Deep Dive
CSP prevents:
- XSS (cross-site scripting) by restricting script sources
- Data injection by restricting where content loads from
- Clickjacking via frame-ancestors
- Protocol downgrade attacks via upgrade-insecure-requests
The tricky part: Next.js needs 'unsafe-inline' for styles and
'unsafe-eval' for some optimizations. Mitigate with nonces.
CSP with Nonces (More Secure)
// middleware.ts -- generate nonce per request
import { NextRequest, NextResponse } from 'next/server'
import { nanoid } from 'nanoid'
export function middleware(request: NextRequest) {
const nonce = Buffer.from(nanoid()).toString('base64')
const csp = [
"default-src 'self'",
`script-src 'self' 'nonce-${nonce}' 'strict-dynamic'`,
`style-src 'self' 'nonce-${nonce}'`,
"img-src 'self' blob: data: https:",
"object-src 'none'",
"base-uri 'self'",
"frame-ancestors 'none'",
].join('; ')
const response = NextResponse.next()
response.headers.set('Content-Security-Policy', csp)
response.headers.set('x-nonce', nonce) // Pass to layout
return response
}
// app/layout.tsx -- use nonce on scripts
import { headers } from 'next/headers'
export default function RootLayout({ children }) {
const nonce = headers().get('x-nonce')
return (
<html>
<head>
<script nonce={nonce} src="/my-script.js" />
</head>
<body>{children}</body>
</html>
)
}
Testing Your Headers
# Check headers from terminal
curl -I https://yourapp.com
# Online scanners:
# securityheaders.com -- grades your headers A-F
# observatory.mozilla.org -- Mozilla's scanner
# developer.chrome.com/docs/lighthouse -- Lighthouse audit
# Target: A rating on securityheaders.com
Headers for API Routes
// API routes need different headers than pages
{
source: '/api/(.*)',
headers: [
{ key: 'X-Content-Type-Options', value: 'nosniff' },
{ key: 'X-Frame-Options', value: 'DENY' },
// No CSP needed for API routes -- they return JSON, not HTML
// CORS headers added separately per route
]
}
Pre-Configured in the AI SaaS Starter Kit
Security headers configured in next.config.js with sensible defaults for a SaaS app. A-grade on securityheaders.com out of the box.
$99 one-time at whoffagents.com
Top comments (0)