webpack 构建中 runtime 脚本必须显式绑定 __webpack_nonce__,即在入口 js 顶部声明 window.__webpack_nonce__ = "abc123",且该值须与 csp 响应头及 html script 标签中的 nonce 逐字节一致。

Webpack 构建中 runtime 脚本必须显式绑定 __webpack_nonce__
Webpack 自动生成的内联 runtime(如 __webpack_require__ 启动逻辑、HMR 初始化代码)默认不带 nonce 属性,哪怕你手动给 HTML 中的 <script></script> 加了 nonce="abc123",这些脚本仍会被 CSP 拦截,导致白屏或 hydration 失败。
正确做法是在入口 JS 文件最顶部(任何模块执行前)声明全局变量:
window.__webpack_nonce__ = "abc123";
注意:该值必须与服务端本次响应头中的 Content-Security-Policy: script-src 'nonce-abc123' 和 HTML 中 <script nonce="abc123"></script> 的字符串**逐字节一致**;不能用 document.currentScript?.getAttribute('nonce') 动态读取——解析阶段早已结束。
- 若使用 HtmlWebpackPlugin,需在
templateParameters中注入 nonce,并确保模板中<script nonce="<%= nonce %>"></script>不被 HTML 转义(EJS 用,Jinja2 用{{ nonce|safe }}) - 禁止在构建时硬编码 nonce 值(如
nonce: "static123"),否则失去“一次性”语义,CSP 形同虚设 - 多入口场景下,每个入口 JS 都要重复设置
__webpack_nonce__,Webpack 不会自动广播
Vite 构建中 html.cspNonce 配置只管标签,不管逻辑
Vite 的 html.cspNonce 选项(vite.config.ts 中)会自动为所有 <script></script> 和 <link rel="modulepreload"> 标签注入 nonce 属性,但它**不生成值、不控制响应头、也不保证 runtime 脚本实际执行时能拿到该值**。
常见失效场景是:构建产物里 <link rel="modulepreload" nonce="xyz"> 确实存在,但该预加载脚本对应的是 runtime 逻辑,而 CSP 响应头没配 script-src 'nonce-xyz',或大小写/空格不一致。
-
html.cspNonce值必须由服务端动态传入(如通过 SSR 上下文),不能在 vite.config.ts 中写死字符串 - 若启用
build.rollupOptions.output.manualChunks,需确保生成的内联 chunk 脚本也通过模板渲染输出,而非由 Rollup 自动注入到 HTML -
<link rel="modulepreload">只有在as="script"时才触发 nonce 校验;Vite 默认不加as,需手动补全或改用build.rollupOptions.output.inlineDynamicImports = true
React/Vue SSR 框架中 nonce 必须透出到模板上下文
框架自身不感知 CSP nonce,renderToString 或 renderToNodeStream 输出的 HTML 若没把服务端生成的 nonce 注入到模板变量,那么即使你在 JSX/Template 中写了 <script nonce="{nonce}"></script>,最终也会渲染成空值或转义后的乱码。
关键不是“组件里能不能写 nonce”,而是“服务端有没有把 nonce 作为 context 或 locals 传下去”。例如:
- Express + EJS:在
res.render('index', { nonce: req.nonce })后,模板中用<script nonce="<%= nonce %>"></script> - Next.js App Router:需在
generateStaticParams或generateMetadata外围做 SSR 适配,或改用 Pages Router 的getServerSideProps显式注入 - Vue SSR(如 Nuxt):在
entry-server.js中通过context.nonce = nonceValue透出,模板中用<script :nonce="$ssrContext.nonce"></script>
漏掉框架自动生成的初始化脚本(如 window.__INITIAL_STATE__、__NEXT_DATA__)的 nonce 注入,是最隐蔽的失败点——它们也是内联脚本,同样受 CSP 约束。
构建产物中第三方 SDK 的 nonce 依赖初始化链路
像客服弹窗、埋点 SDK、A/B 测试工具等,常通过 document.createElement('script') 动态挂载。仅靠 strict-dynamic 不够,因为浏览器要求首个执行的内联脚本必须带 nonce,且后续动态创建的 script 必须显式赋值 script.nonce = "abc123"。
但 SDK 自身不读取页面 nonce,也不会主动设置。真正起效的方式只有两种:
- 把 SDK 的初始化逻辑写成服务端渲染的内联脚本:
<script nonce="abc123">window._sdk = { init: ... }; _sdk.init();</script> - 在构建时将服务端 nonce 注入为全局变量(如
window.__csp_nonce__ = "abc123"),再让 SDK 入口 JS 在执行第一行就执行__webpack_nonce__ = window.__csp_nonce__(Webpack)或手动设置script.nonce(原生场景) - 禁用
dangerouslySetInnerHTML或v-html插入 SDK 脚本——它们生成的<script></script>不继承 nonce,必须显式传入当前请求值
复杂点在于:构建工具打包的 SDK bundle 里若有内联代码(如立即执行函数),这部分仍需 nonce;而多数 SDK 发布包不预留 nonce 注入接口,只能靠构建时重写或 wrapper 封装。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











