Next.js security headers checklist: what to ship first (HSTS, CSP Report-Only, COOP/COEP)
A practical checklist for security headers in Next.js. Start with low-breakage headers, add HSTS only when HTTPS is guaranteed, stage CSP in Report-Only, and apply COOP/COEP/CORP only on routes that need cross-origin isolation.
Table of Contents
- Conclusion
- Explanation
- Practical Guide
- Step 1: inventory external dependencies (before CSP)
- Step 2: ship safe baseline headers (usually low risk)
- Step 3: add HSTS (only if HTTPS is guaranteed)
- Step 4: add CSP in Report-Only mode
- Step 5: apply COOP/COEP/CORP only where needed
- Step 6: rollout strategy (ops)
- Pitfalls
- Checklist
- FAQ
- Q1. Can I just use a package like helmet and be done?
- Q2. Why should CSP start in Report-Only?
- Q3. When do I need COOP/COEP?
- Internal links
- References
What security headers should you ship in Next.js, and in what order?
Conclusion
Ship security headers in this order (low-breakage → high-breakage):
- Safe baseline headers (nosniff, referrer policy, permissions policy)
- HSTS (only when HTTPS is guaranteed everywhere)
- CSP in Report-Only (observe → tighten → enforce)
- COOP/COEP/CORP only on routes that truly need cross-origin isolation
The practical rule: one header change per deploy, with rollback.
Explanation
Security headers are a contract between your app and the browser. The risk is not “adding headers”. The risk is breaking production flows:
- auth redirects
- analytics tags
- embedded content
- CDNs/fonts/images
Treat header changes as ops rollouts:
- stage
- measure
- tighten
- enforce
Practical Guide
Step 1: inventory external dependencies (before CSP)
List what your app loads cross-origin:
- analytics (GA4/GTM)
- images/CDN (Cloudinary/S3/img proxy)
- fonts (Google Fonts/self-host)
- embeds (YouTube/Stripe/maps)
- auth redirects
This inventory becomes your allowlist.
Step 2: ship safe baseline headers (usually low risk)
Common safe set:
X-Content-Type-Options: nosniffReferrer-Policy: strict-origin-when-cross-originPermissions-Policy: ...X-Frame-Options: DENY(only if you never embed)
Next.js example:
// next.config.ts
import type { NextConfig } from 'next';
const nextConfig: NextConfig = {
async headers() {
return [
{
source: '/(.*)',
headers: [
{ key: 'X-Content-Type-Options', value: 'nosniff' },
{ key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
{ key: 'X-Frame-Options', value: 'DENY' },
{
key: 'Permissions-Policy',
value: 'camera=(), microphone=(), geolocation=(), payment=()',
},
],
},
];
},
};
export default nextConfig;
Decision rule:
- If you need embedding, avoid
X-Frame-Options: DENYand use CSPframe-ancestorsinstead.
Step 3: add HSTS (only if HTTPS is guaranteed)
HSTS can lock users into HTTPS, which is good—until you still have HTTP somewhere.
Example:
Strict-Transport-Security: max-age=15552000; preload
Decision rule:
- Do not ship HSTS if any subdomain or endpoint is still HTTP.
Step 4: add CSP in Report-Only mode
Start with:
Content-Security-Policy-Report-Only
Do not start with enforce mode.
Minimal starter:
default-src 'self';
base-uri 'self';
object-src 'none';
frame-ancestors 'none';
img-src 'self' data: https:;
script-src 'self' 'unsafe-inline' https:;
style-src 'self' 'unsafe-inline' https:;
Then tighten based on reports:
- reduce
'unsafe-inline' - pin hosts (avoid permanent
https:) - migrate scripts toward nonces/hashes
Step 5: apply COOP/COEP/CORP only where needed
These are for cross-origin isolation (e.g., SharedArrayBuffer). They are easy to break.
Cross-Origin-Opener-Policy: same-originCross-Origin-Embedder-Policy: require-corpCross-Origin-Resource-Policy: same-site
Decision rule:
- Apply them only on routes that require cross-origin isolation (e.g.
/app/isolation/*).
Step 6: rollout strategy (ops)
- ship one change per deploy
- keep a rollback toggle
- monitor errors and business flows
Pitfalls
- Copy-pasting a strict CSP and breaking auth/analytics
- Enabling HSTS while HTTP still exists somewhere
- Enabling COEP globally and breaking embeds/CDNs
- Using
X-Frame-Optionswhile you need embedding
Checklist
- [ ] External dependencies are inventoried (analytics/auth/cdn/fonts/embeds)
- [ ] Safe baseline headers are shipped
- [ ]
X-Frame-Optionsdecision is made (embed needed or not) - [ ] HTTPS guarantee is verified before shipping HSTS
- [ ] HSTS is staged (max-age strategy) and documented
- [ ] CSP starts in Report-Only
- [ ] CSP reports are collected and reviewed
- [ ] CSP allowlists are tightened (hosts pinned)
- [ ] Script strategy exists (externalize / nonce / hashes)
- [ ] COOP/COEP/CORP are scoped only to isolation routes (if used)
- [ ] Rollback toggle exists for each header group
FAQ
Q1. Can I just use a package like helmet and be done?
It helps, but you still need decisions: embedding policy, CSP allowlists, and rollout strategy. The hard part is app-specific.
Q2. Why should CSP start in Report-Only?
Because CSP is app-specific. Report-Only lets you observe real dependencies before enforcing and breaking flows.
Q3. When do I need COOP/COEP?
Only when you need cross-origin isolation features (like SharedArrayBuffer). If you don’t know, don’t ship them globally.
Internal links
- Parent hub: AI development: start here
- Related:
References
- MDN: Content Security Policy: https://developer.mozilla.org/docs/Web/HTTP/CSP
- MDN: Strict-Transport-Security (HSTS): https://developer.mozilla.org/docs/Web/HTTP/Headers/Strict-Transport-Security
- MDN: Permissions-Policy: https://developer.mozilla.org/docs/Web/HTTP/Headers/Permissions-Policy
- Web.dev: Cross-origin isolation: https://web.dev/cross-origin-isolation-guide/
Popular
- 1Permit2 explained (Web3): why approvals changed and how to use it safely (checklist)
- 2Read wallet signing screens (Web3): a 30-second checklist to avoid permission traps
- 3Spec-to-implementation prompt template (AI development): how to stop the model from guessing
- 4Revoke token approvals on EVM: how to audit allowances safely (checklist)
- 5Clarifying questions checklist (AI development): what to ask before you let an LLM build