Harden a Next.js app with HTTP security headers — set X-Content-Type-Options, X-Frame-Options, Referrer-Policy, HSTS, and Permissions-Policy in next.config, and configure CORS for your route handlers.
Why: a handful of response headers tell the browser to lock things down, and you set them all in one place with the async headers() function in next.config. source: "/(.*)" applies them to every route. nosniff stops the browser guessing a file's type, SAMEORIGIN blocks other sites from putting yours in an iframe (clickjacking), and the Referrer-Policy trims what you leak in the Referer header.
// next.config.ts
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
async headers() {
return [
{
source: '/(.*)', // every route
headers: [
{ key: 'X-Content-Type-Options', value: 'nosniff' },
{ key: 'X-Frame-Options', value: 'SAMEORIGIN' },
{
key: 'Referrer-Policy',
value: 'strict-origin-when-cross-origin',
},
],
},
]
},
}
export default nextConfigWhy: Strict-Transport-Security (HSTS) tells the browser to only ever talk to your site over HTTPS — even if a user types http://. Set a long max-age and include subdomains. Note: your host usually terminates HTTPS for you; this header makes the browser enforce it on every future visit. Add it to the same headers array.
{
key: 'Strict-Transport-Security',
value: 'max-age=63072000; includeSubDomains; preload',
}Why: Permissions-Policy switches off browser features your app does not use — camera, microphone, geolocation — so a compromised or injected script cannot quietly turn them on. List only what you actually need; an empty () denies the feature to everyone.
{
key: 'Permissions-Policy',
value: 'camera=(), microphone=(), geolocation=()',
}Note: CORS controls which other origins may call your API from the browser. By default the browser blocks cross-origin requests; you opt specific origins in with Access-Control-Allow-* headers, scoped to /api/:path*. Prefer an explicit origin over "*" unless the endpoint is genuinely public — "*" lets any site call it.
// next.config.ts — allow one known origin to call your API
async headers() {
return [
{
source: '/api/:path*',
headers: [
{
key: 'Access-Control-Allow-Origin',
value: 'https://app.example.com', // not '*' unless fully public
},
{
key: 'Access-Control-Allow-Methods',
value: 'GET, POST, PUT, DELETE, OPTIONS',
},
{
key: 'Access-Control-Allow-Headers',
value: 'Content-Type, Authorization',
},
],
},
]
}