SSR/SSG混合场景下nonce必须每次请求动态生成,不可复用SSG预生成的静态值;服务端响应头与HTML中script标签的nonce值须字符级完全一致,否则CSP拦截脚本执行。

SSR/SSG混合场景下 nonce 必须每次请求生成,不能复用
SSG(静态站点生成)本身不产生动态 nonce,但一旦页面含 SSR 组件(如 Next.js 的 getServerSideProps、SvelteKit 的 load 函数),该页面就变成“按需渲染”,服务端必须为每次响应生成唯一 nonce。若你把 SSG 构建时预生成的固定 nonce 硬编码进 HTML 模板,CSP 会立刻失效——浏览器看到的是静态值,而服务端响应头里是新值,两者必然不匹配。
常见错误现象:Refused to execute inline script because it violates the following Content Security Policy directive,且 DevTools Network → Headers 中的 Content-Security-Policy 值与源码中 <script nonce="..."></script> 完全不同。
- Next.js:禁用
getStaticProps+revalidate混用时的 nonce 复用——即使页面标记为“静态”,只要用了 SSR 数据获取逻辑,就必须走动态 nonce 流程 - SvelteKit:在
src/routes/+page.server.ts中调用crypto.randomUUID()或crypto.randomBytes(16).toString('base64url')生成 nonce,并通过stuff或locals注入模板 - 构建时(如 Vite 预渲染)若强行注入 nonce,必须确保构建脚本不缓存或复用 nonce 字符串;否则所有生成的 HTML 文件都带同一个值,等同于关闭 CSP
SSG 页面中内联脚本必须显式携带 nonce,不能依赖构建工具自动注入
Webpack/Vite/Rollup 在构建阶段注入的 runtime 脚本(如 __webpack_require__ 启动代码)、HMR 代码、或框架 hydration 脚本,默认不带 nonce 属性。这些脚本一旦执行,就会被 CSP 拦截,导致 hydration 失败、交互不可用。
使用场景:你用 vite build --ssr 生成静态 HTML,但页面里有 <script>console.log("init")</script> 这类手写内联脚本,或者框架自动生成的初始化脚本。
立即学习“前端免费学习笔记(深入)”;
- Next.js:启用
experimental.outputStandalone后,务必检查生成的standalone/server/index.js是否将 nonce 正确注入到<script></script>标签中;手动插入的脚本需写成<script nonce={process.env.NONCE}>...</script> - Sapper/SvelteKit:模板中必须显式写
<script nonce="%sapper.cspnonce%"></script>或<script nonce={data.nonce}></script>,不能只靠sapper export自动处理 - 纯静态 HTML(如 Hugo/Jekyll 输出)若要支持 CSP nonce,必须在构建后处理阶段用脚本批量重写
<script></script>标签——但更推荐改为外链脚本,避开 nonce 管理复杂度
SSG 中 dangerouslySetInnerHTML / v-html 内容无法继承 nonce,必须手动传入
当 SSR 渲染的 HTML 包含用户提交的富文本(如 Markdown 渲染结果),并通过 dangerouslySetInnerHTML(React)或 v-html(Vue)插入 DOM 时,这些内容里的内联 <script> 标签不会自动获得当前请求的 nonce。它们会被 CSP 拒绝,哪怕服务端和响应头完全正确。
根本原因:这些 API 只做字符串插入,不触发框架的 nonce 注入逻辑;浏览器解析时,它看到的是没有 nonce 属性的 <script>,直接判定为不信任。
- 修复方式不是给整个
div加nonce,而是对富文本内容做预处理:提取其中的<script>片段,为其显式添加nonce="xxx"属性,再拼回 HTML 字符串 - 更安全的做法是禁止富文本中包含
<script>—— 使用 HTML 净化库(如DOMPurify)移除所有脚本标签,仅保留白名单内的元素和属性 - Goober 等 CSS-in-JS 库依赖
window.__nonce__,但该变量必须由服务端在首个<script nonce="...">中设置,且不能被 SSG 构建过程剥离或混淆
SSG 构建产物中 meta CSP 与 script nonce 不一致的检查盲区
很多人用 <meta http-equiv="Content-Security-Policy"> 替代响应头来声明 CSP,尤其在纯静态托管(如 GitHub Pages、Cloudflare Pages)上。但这个做法在 SSG 场景下极易出错:meta 标签只能写死一个值,而每个 SSR 页面请求需要不同的 nonce,导致 meta 值永远滞后或固定,与实际脚本不匹配。
性能影响:meta CSP 的优先级低于响应头,且部分浏览器(如 Safari)对 meta CSP 支持不完整;一旦响应头未设置,仅靠 meta 标签,script-src 'nonce-...' 可能被忽略。
- 强制要求:SSG + SSR 混合部署时,CSP 必须通过 HTTP 响应头传递,不能依赖
<meta>;静态页面若无 SSR,则不应使用 nonce,改用哈希('sha256-...')或完全避免内联脚本 - 检查点:curl -I https://yoursite.com/page-with-ssr | grep "Content-Security-Policy",确认输出中包含
script-src 'nonce-且值与该页面源码中<script nonce="...">完全一致(注意 base64url 编码中的+、/、=是否被模板引擎误转义) - 容易被忽略的地方:Nginx 等反向代理可能覆盖或清空后端设置的 CSP 头——需确认 proxy_pass 后是否保留了原始响应头



















