Next Starter Logo
Security

Security Headers & CSP

How Next Starter configures HTTP security headers, including Content Security Policy, HSTS, X-Frame-Options, Referrer-Policy, and more, inside next.config.ts.

Overview

Next Starter configures HTTP security headers in next.config.ts. These headers apply to all routes (except the sitemap) via the Next.js headers async function.

The poweredByHeader option is disabled to avoid advertising the framework to potential attackers.

Header Configuration

All headers are defined in the securityHeaders array in next.config.ts and applied globally:

next.config.ts
const securityHeaders = [
  { key: "X-DNS-Prefetch-Control", value: "on" },
  { key: "X-Content-Type-Options", value: "nosniff" },
  {
    key: "Strict-Transport-Security",
    value: "max-age=63072000; includeSubDomains; preload",
  },
  { key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
  { key: "X-Frame-Options", value: "SAMEORIGIN" },
  {
    key: "Content-Security-Policy",
    value: [
      "default-src 'self'",
      "object-src 'none'",
      "base-uri 'self'",
      "form-action 'self'",
      `img-src 'self' data: blob: https://*.googleusercontent.com https://${cdnHost}`,
      `script-src 'self' 'unsafe-inline'${isDev ? " 'unsafe-eval'" : ""} https://www.google.com/recaptcha/ https://www.gstatic.com/recaptcha/`,
      "style-src 'self' 'unsafe-inline' https://fonts.googleapis.com",
      "font-src 'self' https://fonts.gstatic.com",
      "connect-src 'self' https://*.r2.cloudflarestorage.com https://www.google.com/recaptcha/",
      "frame-ancestors 'self'",
      "frame-src 'self' https://www.google.com/recaptcha/",
      "upgrade-insecure-requests",
    ].join("; "),
  },
  { key: "Cross-Origin-Opener-Policy", value: "same-origin" },
];

const nextConfig: NextConfig = {
  poweredByHeader: false,

  headers: async () => [
    { source: "/((?!sitemap).*)", headers: securityHeaders },
    ...noIndexRoutes.map((route) => ({
      source: route,
      headers: [...securityHeaders, ...getNoIndexHeaders()],
    })),
    {
      source: "/manifest.webmanifest",
      headers: [{ key: "Cache-Control", value: "public, max-age=86400, stale-while-revalidate=43200" }],
    },
  ],
};

Content Security Policy

The CSP is built as a single header value by joining directives with "; ". Each directive controls which sources are trusted for a specific resource type.

Directives

DirectiveValuePurpose
default-src'self'Fallback for resource types not explicitly listed
object-src'none'Blocks Flash and other plugin content
base-uri'self'Prevents base tag injection attacks
form-action'self'Restricts where forms can submit
img-src'self' data: blob: https://*.googleusercontent.com https://<cdn>Allows your CDN and Google profile images
script-src'self' 'unsafe-inline' https://www.google.com/recaptcha/ https://www.gstatic.com/recaptcha/Scripts from your origin and reCAPTCHA (both google.com and gstatic.com)
style-src'self' 'unsafe-inline' https://fonts.googleapis.comInline styles and Google Fonts CSS
font-src'self' https://fonts.gstatic.comSelf-hosted and Google Fonts files
connect-src'self' https://*.r2.cloudflarestorage.com https://www.google.com/recaptcha/Fetch/XHR targets including R2 direct uploads
frame-ancestors'self'Equivalent to X-Frame-Options SAMEORIGIN
frame-src'self' https://www.google.com/recaptcha/Allows the reCAPTCHA iframe
upgrade-insecure-requests(no value)Upgrades HTTP sub-resource requests to HTTPS

Development vs Production

In development, 'unsafe-eval' is added to script-src to support fast refresh and source maps:

`script-src 'self' 'unsafe-inline'${isDev ? " 'unsafe-eval'" : ""} ...`

This is automatically removed in production builds.

Adding a New External Domain

If you integrate a third-party service that loads scripts, styles, or makes network requests, extend the relevant CSP directive:

// Example: adding an analytics script from example.com
`script-src 'self' 'unsafe-inline' https://cdn.example.com https://www.google.com/recaptcha/ https://www.gstatic.com/recaptcha/`,
`connect-src 'self' https://api.example.com https://*.r2.cloudflarestorage.com https://www.google.com/recaptcha/`,

HSTS

The Strict-Transport-Security header enforces HTTPS for all connections:

Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
  • max-age=63072000: 2-year duration, the recommended minimum for preload eligibility
  • includeSubDomains: Applies the policy to all subdomains
  • preload: Signals intent to be included in browser preload lists

Once a browser sees this header, it refuses plain HTTP connections to your domain for the duration of max-age, even before the first HTTPS request on a fresh visit.

Do not add the preload directive unless you intend to submit your domain to hstspreload.org. Removing a domain from the preload list is a slow process.

X-Frame-Options

X-Frame-Options: SAMEORIGIN

Prevents your pages from being embedded in <iframe>, <frame>, or <object> elements on external domains, which mitigates clickjacking attacks. The CSP frame-ancestors 'self' directive provides the same protection for modern browsers. Both are included for compatibility.

X-Content-Type-Options

X-Content-Type-Options: nosniff

Instructs browsers not to MIME-sniff responses. Without this header, some browsers execute files as a different content type than declared, for example treating a text file as JavaScript. Always set this header.

Referrer-Policy

Referrer-Policy: strict-origin-when-cross-origin

Controls how much referrer information is included in requests:

  • Full URL is sent for same-origin requests
  • Only the origin (scheme + host) is sent for cross-origin HTTPS-to-HTTPS requests
  • No referrer is sent when navigating from HTTPS to HTTP

X-DNS-Prefetch-Control

X-DNS-Prefetch-Control: on

Allows the browser to perform DNS lookups for resources on the page in advance, improving performance for external resources like fonts and CDN assets.

Cross-Origin-Opener-Policy

Cross-Origin-Opener-Policy: same-origin

Isolates the browsing context from cross-origin documents. This prevents cross-origin windows from accessing your document's global object and is a prerequisite for enabling certain high-resolution timer and SharedArrayBuffer APIs. The same-origin value is the strictest setting.

No-Index Routes

Certain routes receive X-Robots-Tag: noindex, nofollow in addition to the security headers. Configure these routes via APP_CONFIG.noIndexRoutes in lib/config.ts:

lib/config.ts
noIndexRoutes: [
  "/auth/:path*",
  "/api/:path*",
  "/dashboard/:path*",
  "/privacy",
  "/terms",
  "/500",
  "/error",
  "/auth/error",
],

In next.config.ts, each route receives both the standard security headers and the noindex header:

...noIndexRoutes.map((route) => ({
  source: route,
  headers: [...securityHeaders, ...getNoIndexHeaders()],
})),

Testing Headers

Using curl

curl -I https://yourdomain.com

Look for Content-Security-Policy, Strict-Transport-Security, and other headers in the response.

Using an Online Tool

securityheaders.com provides a graded report of your configured headers with explanations for missing or misconfigured values.

Browser DevTools

Open DevTools, navigate to the Network tab, select any document request, and inspect the Response Headers section. All security headers appear there.

On this page