Next.js配置严格CSP导致预加载脚本被拦截如何解决
严格CSP下Next.js预加载chunk被拦截的修复方案
拦截原因
- Next.js自动生成的
<link rel="preload" as="script">标签默认需要匹配配置的nonce才能通过CSP校验,但原配置中nonce值是在组件render方法中生成的,而Next.js的内联脚本源码获取、预加载标签生成逻辑都是在getInitialProps阶段执行的,两个阶段的上下文独立,生成的nonce值不匹配,导致预加载标签携带的nonce和CSP规则里声明的nonce对不上,直接触发拦截。 - 原先通过
<meta httpEquiv="Content-Security-Policy">传递CSP规则的方式存在兼容缺陷:浏览器对meta形式的CSP不会完全启用strict-dynamic的全量校验逻辑,同时在script-src中追加的https: http: 'unsafe-inline' 'unsafe-eval'配置,既会破坏严格CSP的安全边界,也无法让预加载标签通过校验——存在strict-dynamic时,普通scheme类的源规则会被浏览器自动忽略。
修复步骤
移除CSP规则中多余的安全冗余配置:
script-src字段删除'unsafe-inline'、'unsafe-eval'、https:、http:这几个值。严格CSP模式下,nonce存在时unsafe-inline会被浏览器自动忽略,全scheme通配和eval授权会直接抵消严格CSP的防护效果。
调整nonce的生成时机:
- 将nonce的生成逻辑从
render方法移到getInitialProps阶段,保证和Next.js的内联脚本计算、预加载标签生成在同一个上下文执行,确保两边使用的nonce值完全一致。
- 将nonce的生成逻辑从
调整CSP的传递方式:
- 放弃在
<Head>中插入meta标签传递CSP的写法,改为在服务端渲染阶段通过服务端响应头设置CSP规则。响应头形式的CSP规则校验逻辑完全符合W3C标准,不会出现meta形式下预加载资源校验失效的问题。
- 放弃在
给所有Next.js注入的资源标签匹配nonce:
- 将同一上下文中生成的nonce从
getInitialProps阶段传递到组件props,给<NextScript>组件传入nonce值,服务端渲染阶段会自动给所有生成的预加载标签、脚本标签补充对应的nonce属性。
修正后的
_document.js核心参考代码:import Document, { Html, Head, Main, NextScript } from 'next/document' import crypto from 'crypto' const cspHashOf = (text) => { const hash = crypto.createHash('sha256') hash.update(text) return `'sha256-${hash.digest('base64')}'` } export default class MyDocument extends Document { static async getInitialProps(ctx) { // 同一上下文生成nonce值,保证和Next.js资源生成逻辑使用同一份值 const nonce = crypto.randomBytes(16).toString('base64') // 定义严格CSP规则 const cspConfig = { 'object-src': "'none'", 'base-uri': "'self'", 'script-src': `'nonce-${nonce}' 'strict-dynamic' ${cspHashOf( NextScript.getInlineScriptSource(ctx) )}`, } // 服务端设置CSP响应头 const cspStr = Object.entries(cspConfig).map(([key, value]) => `${key} ${value}`).join('; ') if (ctx.res) { ctx.res.setHeader('Content-Security-Policy', cspStr) } const initialProps = await Document.getInitialProps(ctx) // 把nonce传到组件渲染props return { ...initialProps, nonce } } render() { const { nonce } = this.props return ( <Html> <Head /> <body> <Main /> {/* 传入nonce给NextScript,自动给所有生成的脚本、预加载标签注入对应nonce */} <NextScript nonce={nonce} /> </body> </Html> ) } }- 将同一上下文中生成的nonce从
特殊场景适配:
- 如果开发环境需要使用eval能力(比如热更新调试),可以在开发环境下动态给
script-src追加'unsafe-eval'值,生产环境绝对不要保留该配置。
- 如果开发环境需要使用eval能力(比如热更新调试),可以在开发环境下动态给
注意:配置完成后需要重启Next.js服务,清理之前的构建缓存,否则旧的不带nonce的预加载标签可能仍然会被拦截。
内容的提问来源于stack exchange,提问作者Abby
相关产品推荐
相关产品推荐

