Tuning the security of a Next.js site
On Next.js you set the headers in your own config or middleware, not in a CMS. Below, per finding, where the fix lives, with code you can copy.
Response headers (HSTS, clickjacking, MIME sniffing, referrer, permissions)
Set these in next.config.js via the headers() function, which applies to every path. Note: a static export (output: "export") serves no headers from Next, so then they belong on the host (a _headers file on Netlify or Cloudflare Pages, or vercel.json, say).
const securityHeaders = [
{ key: 'Strict-Transport-Security', value: 'max-age=31536000; includeSubDomains; preload' },
{ key: 'X-Frame-Options', value: 'SAMEORIGIN' },
{ key: 'X-Content-Type-Options', value: 'nosniff' },
{ key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
{ key: 'Permissions-Policy', value: 'camera=(), microphone=(), geolocation=()' },
];
module.exports = {
async headers() {
return [{ source: '/:path*', headers: securityHeaders }];
},
};The Content-Security-Policy (with a nonce)
A strict CSP in Next is best set with a per-request nonce via middleware, so you can allow inline scripts without unsafe-inline. Pass the nonce to your scripts.
- Set the policy in report-only first (the Content-Security-Policy-Report-Only header), so it breaks nothing but still reports what it would block.
- Collect the reports with a tool like Sentry, Report URI or URIports, or read them in your browser console.
- Allow what is legitimately blocked, until no real violations come in.
- Rename the header to Content-Security-Policy to enforce the policy.
import { NextResponse } from 'next/server';
export function middleware() {
const nonce = Buffer.from(crypto.randomUUID()).toString('base64');
const csp = [
"default-src 'self'",
`script-src 'self' 'nonce-${nonce}' 'strict-dynamic'`,
"object-src 'none'",
"base-uri 'self'",
].join('; ');
const response = NextResponse.next();
response.headers.set('Content-Security-Policy-Report-Only', csp);
response.headers.set('x-nonce', nonce);
return response;
}In the page (a hash on a script, trackers after consent)
An integrity hash, or allowing an external script, belongs on the component that renders the script (next/script, say). Trackers that may load only after consent are loaded through a consent manager or a tag manager, not directly in the component.
The connection (HTTPS and the certificate)
Forcing HTTPS and the certificate are your host or CDN. Make sure http redirects to https and the certificate is valid and not expired. Most hosts and CDNs do this with a toggle or a free Let's Encrypt certificate.
DNS records (SPF, DMARC, DNSSEC, IPv6)
These sit apart from your site: they are records at your domain. Set them with your DNS provider or domain registrar, not in your CMS or on your host. SPF and DMARC govern who may send mail as your domain, DNSSEC signs your DNS, and IPv6 is the one where the host has to offer an AAAA record and a connection before you can add it.