Next.js _app已有getInitialProps时如何共享参数到_document解决CSP问题
Next.js自定义App.getInitialProps场景下CSP内联样式报错解决方案
核心认知纠正
自定义_app的getInitialProps不会阻断_document的getInitialProps执行,绝大多数情况下该问题是因为自定义_app.getInitialProps时没有正确调用父类的App.getInitialProps方法导致的。你尝试挂载到req对象的参数是可以正常跨_app和_document共享的,调整方法如下:
方案1:请求上下文req对象传参(最兼容SSR场景)
步骤1:修正_app的getInitialProps逻辑
必须先调用父类方法保证文档渲染链路正常,再挂载nonce到req对象:
import App, { AppContext } from 'next/app' MyApp.getInitialProps = async (appContext: AppContext) => { try { // 必须先调用父类getInitialProps,才能触发_document的执行逻辑 const initProperties = await App.getInitialProps(appContext) //... 原有业务逻辑 const [csp, nonce] = generateCsp(); const res = appContext?.ctx.res; if (res != null) { res.setHeader('Content-Security-Policy', csp); } // 每个请求的req对象独立,不会出现并发污染 (appContext.ctx.req as any).nonce = nonce; return { ...initProperties, nonce, }; } catch (error) { return { error, }; } };
步骤2:在_document中读取nonce
直接从getInitialProps的上下文里拿到同一个req对象,读取挂载的nonce即可使用:
import Document, { DocumentContext, Html, Head, Main, NextScript } from 'next/document' export default class MyDocument extends Document { static async getInitialProps(ctx: DocumentContext) { const initialProps = await Document.getInitialProps(ctx) // 读取_app挂载的nonce,降级为空字符串兼容边缘场景 const nonce = (ctx.req as any)?.nonce || '' return { ...initialProps, nonce, } } render() { const { nonce } = this.props return ( <Html lang="en" dir="ltr"> <Head nonce={nonce}> {/* 其他头部配置 */} </Head> <body> <Main /> {/* 注意给NextScript也加上nonce,避免脚本执行被CSP拦截 */} <NextScript nonce={nonce} /> </body> </Html> ) } }
方案2:Middleware统一处理(Next.js 12.2+推荐)
如果不想调整现有_app的业务逻辑,可以把nonce生成逻辑移到Middleware中:
- 每个请求进入时在Middleware生成nonce,设置CSP响应头,同时将nonce存入自定义请求头
X-Nonce - 在
_document.getInitialProps中直接从请求头读取nonce使用
该方案和业务逻辑完全解耦,不需要修改_app的现有代码。
无nonce替代方案:内联样式哈希白名单
如果不想维护nonce逻辑,可以提取Next.js默认注入的内联样式的SHA-256哈希值,添加到CSP的style-src规则中,不需要开启unsafe-inline。缺点是Next.js版本迭代时如果内联样式内容变化,需要同步更新哈希值。
内容的提问来源于stack exchange,提问作者Sabir Moglad
相关产品推荐
相关产品推荐

