Security headers in Next.js (App Router)
HTTP security headers are cheap wins: they reduce clickjacking, MIME sniffing, downgrade attacks, and some classes of XSS—especially when combined with a thoughtful Content-Security-Policy (CSP). Next.js lets you attach headers globally via next.config and refine them per route in middleware.ts.
This post covers what I enable by default, how I roll out CSP without breaking third-party scripts, and what headers cannot replace.
Baseline headers in next.config
/** @type {import('next').NextConfig} */
const securityHeaders = [
{ key: 'X-DNS-Prefetch-Control', value: 'on' },
{ 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=()',
},
];
const nextConfig = {
async headers() {
return [
{
source: '/:path*',
headers: securityHeaders,
},
];
},
};
module.exports = nextConfig;
What each does
- X-Frame-Options: DENY — prevents embedding your site in
<iframe>(clickjacking). UseSAMEORIGINonly if you intentionally iframe yourself. - X-Content-Type-Options: nosniff — stops browsers from MIME-sniffing responses away from declared
Content-Type. - Referrer-Policy — limits referrer leakage on cross-origin navigations.
- Permissions-Policy — disables powerful features by default; tighten per route if you need camera/mic.
Strict-Transport-Security (HSTS)
Only emit HSTS when all production traffic is HTTPS and you will not revert to HTTP:
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
preload is optional and commits you to the browser preload list—read the requirements before enabling.
Content-Security-Policy: staged rollout
CSP is powerful and fragile: one wrong directive blocks analytics, payment widgets, or inline styles.
Step 1: Report-Only
Content-Security-Policy-Report-Only: default-src 'self'; report-uri https://csp.example.com/report
Collect violations in production without enforcing. Tune directives until noise drops.
Step 2: Nonces for inline scripts (App Router)
For first-party inline scripts, prefer nonces over 'unsafe-inline'. Next.js 13+ documents patterns using headers() in middleware or experimental CSP helpers—pick the approach that matches your hosting (Vercel vs self-hosted).
Step 3: Enforce
Flip to Content-Security-Policy once dashboards show green. Keep a fast rollback path (feature flag or config toggle).
Middleware for dynamic headers
When headers depend on the user, tenant, or experiment, middleware.ts can append or override:
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(req: NextRequest) {
const res = NextResponse.next();
res.headers.set('X-Robots-Tag', req.nextUrl.pathname.startsWith('/draft') ? 'noindex' : 'all');
return res;
}
Keep middleware fast: no remote calls unless absolutely necessary—they run on every matched request.
What headers do not fix
- Authentication and authorization bugs.
- SQL injection or command injection from unsanitized input.
- Broken access control in your API.
Headers are defense in depth, not the primary control.
Testing
- Add security header checks to E2E or Playwright global setup (basic presence assertions).
- Use securityheaders.com or Mozilla Observatory for a second opinion.
Summary
Start with a small, strict baseline in next.config, add HSTS when HTTPS is guaranteed, and treat CSP as a staged project with Report-Only and nonces—not a one-line copy-paste. Use middleware when policies vary by route or tenant.