headers()
Attaches custom response headers to paths matching a pattern, before the filesystem is checked.
headers?(): Promise<Header[]> | Header[]
type Header = {
source: string
headers: { key: string; value: string }[]
basePath?: false
locale?: false
has?: RouteHas[]
missing?: RouteHas[]
}- Added in Next.js v9.5.0
Parameters
| Parameter | Type | Description |
|---|---|---|
$source | string | Incoming request path pattern, path-to-regexp syntax, anchored to the start. Matched parameters can be interpolated into both header keys and values. |
$headers | { key: string; value: string }[] | Response headers to set. When two entries match the same path and set the same key, the later entry wins. |
$basePathoptional | false | undefined= undefined | false stops the configured basePath being prefixed to source. |
$localeoptional | false | undefined= undefined | false stops locale prefixes being handled automatically; you must then write the locale into source yourself. |
$hasoptional | RouteHas[] | Header, cookie, host or query conditions that must all match for the headers to be applied. |
$missingoptional | RouteHas[] | Conditions that must all NOT match for the headers to be applied. |
Return value
Promise<Header[]> | Header[]An array of objects pairing a source pattern with the header key/value pairs to add.
Errors
Cache-Control on immutable build assets cannot be overridden; Next.js pins those to public, max-age=31536000, immutable.
Changelog
- PHP v13.3.0missing added.
- PHP v10.2.0has added.
- PHP v9.5.0headers added.
This is where a whole-site security baseline lives — HSTS, X-Content-Type-Options, Referrer-Policy, Permissions-Policy — plus per-path exceptions like CORS on /api/:path*. Headers are evaluated first in the routing chain, before redirects, proxy and the filesystem.
When to reach for it
- Applying a static security header set across every response.
- Opening CORS for Route Handlers consumed by another origin.
- Setting
Cache-Controlon your own static assets inpublic/.
What trips people up
Files under /_next/static carry a content hash and are pinned to public, max-age=31536000, immutable; you cannot override their Cache-Control here, and attempts to do so are silently ignored. Overriding within your own config works the opposite way to CSS specificity: when two matching entries set the same key, the last one wins, so put broad defaults first and narrow overrides after. A CSP with a nonce cannot be expressed here at all, because these values are static per build — that belongs in proxy.ts. Note that it is /(.*) that stops being a universal matcher once locales are configured: it is converted to /(en|fr|de)/(.*) and so will not match the top-level / or /fr routes that /:path* would still match.
Example
import type { NextConfig } from 'next'
const securityHeaders = [
{
key: 'Strict-Transport-Security',
value: 'max-age=63072000; includeSubDomains; preload',
},
{ key: 'X-Content-Type-Options', value: 'nosniff' },
{ key: 'X-Frame-Options', value: 'SAMEORIGIN' },
{ key: 'Referrer-Policy', value: 'origin-when-cross-origin' },
{
key: 'Permissions-Policy',
value: 'camera=(), microphone=(), geolocation=(), browsing-topics=()',
},
]
const nextConfig: NextConfig = {
async headers() {
return [
{
source: '/(.*)',
headers: securityHeaders,
},
]
},
}
export default nextConfigWhen to use it
- Apply HSTS, nosniff and a Referrer-Policy to every route in one place
- Allow a partner origin to call your Route Handlers with CORS headers
- Send a strict Content-Security-Policy on HTML responses only
- Tag responses with the matched slug for downstream logging
Frequently asked questions
Why is my Cache-Control header being ignored?
If the path is under /_next/static, Next.js pins those hashed assets to public, max-age=31536000, immutable and the header cannot be overridden. For your own files, check that no later matching entry sets the same key, since the last match wins.
How do I set a Content-Security-Policy with a nonce?
Not here. Values in next.config are fixed at build time, so a per-request nonce has to be generated in proxy.ts and set on the response there. Use this config for the static parts of your header baseline.
Do these headers apply to files in the public folder?
Yes. Headers are checked before the filesystem, so a source pattern that matches a public/ path applies to it. This is the usual way to add Cache-Control to fonts and images you serve yourself.
More examples
CORS for Route Handlers
Every response under /api gets the three CORS headers. Because the value is a build-time constant, this form can only allow a single fixed origin or the wildcard *. To reflect one of several allowed origins back to the caller, do the check in proxy.ts where the request is available.
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
async headers() {
return [
{
source: '/api/:path*',
headers: [
{ key: 'Access-Control-Allow-Origin', value: 'https://acme.com' },
{
key: 'Access-Control-Allow-Methods',
value: 'GET, POST, PUT, DELETE, OPTIONS',
},
{
key: 'Access-Control-Allow-Headers',
value: 'Content-Type, Authorization',
},
],
},
]
},
}
export default nextConfigOrdering: broad default then narrow override
A request for /hello matches both entries. The last matching entry to set a given key wins, so the response carries x-hello: world, while every other path gets x-hello: there. Reverse the order and the specific rule would be overwritten by the general one — the opposite of what CSS specificity would lead you to expect.
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
async headers() {
return [
{
source: '/:path*',
headers: [{ key: 'x-hello', value: 'there' }],
},
{
source: '/hello',
headers: [{ key: 'x-hello', value: 'world' }],
},
]
},
}
export default nextConfig
Discussion