Security Headers
Built-in HTTP security headers and how to add a Content Security Policy.
What's Included
Your template ships with production-ready security headers in next.config.ts. Every response from your app includes these headers automatically:
| Header | Value | What it prevents |
|---|---|---|
X-Frame-Options | DENY | Clickjacking (your site can't be embedded in iframes) |
X-Content-Type-Options | nosniff | MIME-type sniffing attacks |
Referrer-Policy | strict-origin-when-cross-origin | Leaking full URLs to third parties |
X-DNS-Prefetch-Control | on | N/A (performance, pre-resolves DNS) |
Strict-Transport-Security | max-age=63072000; includeSubDomains; preload | Downgrade attacks (forces HTTPS for 2 years) |
These headers work out of the box with zero configuration.
Verifying Your Headers
After deploying, check your headers with any of these tools:
curl -I https://yourdomain.com
Or use securityheaders.com for a full report.
Adding a Content Security Policy (CSP)
A CSP header is not included by default because it requires tuning for your specific stack. A misconfigured CSP will silently break your site. We recommend adding one after you've finalized your third-party services.
When to add CSP
- You've launched and know exactly which scripts, fonts, and APIs your app uses
- You've removed or finalized your analytics, chat widgets, and other third-party tools
- You want an A+ rating on securityheaders.com
Basic CSP (good starting point)
Add a Content-Security-Policy header to the securityHeaders array in next.config.ts:
const securityHeaders = [
// ... existing headers ...
{
key: 'Content-Security-Policy',
value: [
"default-src 'self'",
"script-src 'self' 'unsafe-inline' 'unsafe-eval'",
"style-src 'self' 'unsafe-inline'",
"img-src 'self' data: blob:",
"font-src 'self'",
"connect-src 'self'",
"frame-src 'self'",
"object-src 'none'",
"base-uri 'self'",
"form-action 'self'",
].join('; '),
},
]
Adding third-party services
Extend each directive based on the services you use:
const cspDirectives = {
'default-src': ["'self'"],
'script-src': [
"'self'",
"'unsafe-inline'", // Required by Next.js
"'unsafe-eval'", // Required by Next.js dev mode, remove in production if possible
],
'style-src': [
"'self'",
"'unsafe-inline'", // Required by Tailwind
],
'img-src': [
"'self'",
'data:',
'blob:',
],
'font-src': ["'self'"],
'connect-src': ["'self'"],
'frame-src': ["'self'"],
'object-src': ["'none'"],
'base-uri': ["'self'"],
'form-action': ["'self'"],
}
Then add sources for each service you use:
| Service | Directives to update |
|---|---|
| Supabase | connect-src: add your *.supabase.co URL. For Realtime, also add wss://*.supabase.co |
| Stripe | script-src: add https://js.stripe.com. frame-src: add https://js.stripe.com. connect-src: add https://api.stripe.com |
| Google Fonts | style-src: add https://fonts.googleapis.com. font-src: add https://fonts.gstatic.com |
| Google Analytics | script-src: add https://www.googletagmanager.com. connect-src: add https://www.google-analytics.com |
| Umami | script-src: add your Umami domain. connect-src: add your Umami domain |
| Vercel Analytics | script-src: add https://va.vercel-scripts.com. connect-src: add https://vitals.vercel-insights.com |
Example: full CSP with Supabase + Stripe
{
key: 'Content-Security-Policy',
value: [
"default-src 'self'",
"script-src 'self' 'unsafe-inline' https://js.stripe.com",
"style-src 'self' 'unsafe-inline'",
"img-src 'self' data: blob: https://*.supabase.co",
"font-src 'self'",
"connect-src 'self' https://*.supabase.co wss://*.supabase.co https://api.stripe.com",
"frame-src 'self' https://js.stripe.com",
"object-src 'none'",
"base-uri 'self'",
"form-action 'self'",
].join('; '),
}
Nonce-based CSP (advanced)
For stronger security, you can use nonces instead of 'unsafe-inline'. This requires Next.js middleware to generate a nonce per request:
- Create
middleware.tsat the project root - Generate a random nonce using
crypto.randomUUID() - Inject the nonce into your CSP header
- Pass the nonce to Next.js via
<Script nonce={nonce} />
See the Next.js CSP docs for the full middleware setup.
Debugging CSP violations
CSP violations show up in the browser console as errors like:
Refused to load the script 'https://example.com/script.js'
because it violates the Content Security Policy directive: "script-src 'self'"
Tip: Start with Content-Security-Policy-Report-Only instead of Content-Security-Policy during development. This logs violations without actually blocking anything, so you can identify all required sources before enforcing the policy.
{
key: 'Content-Security-Policy-Report-Only', // Switch to Content-Security-Policy when ready
value: '...',
}
Additional Headers to Consider
These headers aren't included by default but are worth adding for specific use cases:
| Header | Use case |
|---|---|
Permissions-Policy | Disable browser features you don't use (camera, microphone, geolocation) |
Cross-Origin-Opener-Policy | Isolate your browsing context (set to same-origin if you don't use cross-origin popups) |
Cross-Origin-Resource-Policy | Prevent other sites from loading your resources |
// Example: disable unused browser features
{
key: 'Permissions-Policy',
value: 'camera=(), microphone=(), geolocation=()',
},