headers()

Attaches custom response headers to paths matching a pattern, before the filesystem is checked.

Signature
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

ParameterTypeDescription
$sourcestringIncoming 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.
$basePathoptionalfalse | undefined= undefinedfalse stops the configured basePath being prefixed to source.
$localeoptionalfalse | undefined= undefinedfalse stops locale prefixes being handled automatically; you must then write the locale into source yourself.
$hasoptionalRouteHas[]Header, cookie, host or query conditions that must all match for the headers to be applied.
$missingoptionalRouteHas[]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-Control on your own static assets in public/.

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

Example · typescript
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 nextConfig

When 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.

Example · typescript
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 nextConfig

Ordering: 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.

Example · typescript
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

  • Be the first to comment on this lesson.