Yes, a hash-based CSP approach is excellent for your use case with statically generated pages in the Pages Router! Here's why it's particularly well-suited:
Predictable Content: Since your pages are statically generated, the inline scripts and styles are determined at build time and don't change between requests. This makes hash-based CSP ideal because:
For Pages Router with static generation, you can implement hash-based CSP in your next.config.js:
// Calculate hashes for your inline scripts/styles at build time
const cspHeader = `
default-src 'self';
script-src 'self' 'sha256-[YOUR_SCRIPT_HASH]';
style-src 'self' 'sha256-[YOUR_STYLE_HASH]';
img-src 'self' blob: data:;
font-src 'self';
object-src 'none';
base-uri 'self';
form-action 'self';
frame-ancestors 'none';
upgrade-insecure-requests;
`
module.exports = {
async headers() {
return [
{
source: '/(.*)',
headers: [
{
key: 'Content-Security-Policy',
value: cspHeader.replace(/\n/g, ''),
},
],
},
]
},
}| Aspect | Hash-Based | Nonce-Based |
|---|---|---|
| Static Pages | ✅ Perfect fit | ❌ Overkill |
| Build Complexity | ✅ Simple | ❌ Requires middleware |
| CDN Friendly | ✅ Yes | ❌ Dynamic headers |
| Performance | ✅ No runtime overhead | ❌ Server processing |
unsafe-inline - use hashes for specific inline content you control 2Content-Security-Policy-Report-Only to test without breaking functionality 2Hash-based CSP is the most appropriate choice for your architecture, providing strong security without the complexity of dynamic nonce generation that you don't need for static content.
No, you cannot use hash-based CSP for external scripts loaded with the Next.js <Script> tag. Here's why and what to do instead:
Hashes are only for inline content - they're calculated based on the actual content of inline scripts or styles. External scripts loaded via src attribute work differently in CSP.
For external scripts loaded with <Script>, you need to allowlist the domains in your CSP:
// next.config.js
const cspHeader = `
default-src 'self';
script-src 'self'
https://www.googletagmanager.com
https://www.google-analytics.com
https://cdn.jsdelivr.net;
style-src 'self' 'unsafe-inline';
img-src 'self' blob: data:;
`// This external script requires domain allowlisting, not hashes
<Script
src="https://www.googletagmanager.com/gtag/js?id=GA_MEASUREMENT_ID"
strategy="afterInteractive"
/>
// This inline script would use a hash
<Script id="gtag-init" strategy="afterInteractive">
{`
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'GA_MEASUREMENT_ID');
`}
</Script>const cspHeader = `
default-src 'self';
script-src 'self'
https://www.googletagmanager.com
'sha256-[HASH_FOR_INLINE_GTAG_SCRIPT]';
connect-src 'self'
https://www.google-analytics.com
https://analytics.google.com;
`Use Subresource Integrity (SRI) when possible for additional security:
<Script
src="https://cdn.example.com/script.js"
integrity="sha384-[SRI_HASH]"
crossOrigin="anonymous"
/>connect-src for scripts that make network requestsstrict-dynamic if you need scripts to load other scriptsscript-src'sha256-[hash]'<Script>: Primarily for external scripts, so focus on domain allowlistingYour hash-based approach is still valuable for inline scripts, but you'll need to complement it with domain allowlisting for external scripts loaded via the <Script> component.