React+Emotion开发的NPM包引入Next.js时触发TypeError报错
问题背景
目标是将blinq-image-editor仓库发布的npm包成功导入,在emotion-demo Next.js应用中使用。
当前Next.js应用可以正常编译,但运行时在页面生成阶段会抛出如下服务端错误,所有控制台日志会输出到终端窗口:
Server Error TypeError: Cannot read property 'registered' of null This error happened while generating the page. Any console logs will be displayed in the terminal window. Call Stack <unknown> file:///Users/shertu/shertu/apple/node_modules/.pnpm/blinq-image-editor@1.0.4_hbkw2sf35pylg2cvgnw53hddsi/node_modules/blinq-image-editor/dist/index.js (2:238027) Styled(div) file:///Users/shertu/shertu/apple/node_modules/.pnpm/blinq-image-editor@1.0.4_hbkw2sf35pylg2cvgnw53hddsi/node_modules/blinq-image-editor/dist/index.js (2:147434) renderWithHooks file:///Users/shertu/shertu/apple/node_modules/.pnpm/react-dom@18.2.0_react@18.2.0/node_modules/react-dom/cjs/react-dom-server.browser.development.js (5658:16) renderForwardRef file:///Users/shertu/shertu/apple/node_modules/.pnpm/react-dom@18.2.0_react@18.2.0/node_modules/react-dom/cjs/react-dom-server.browser.development.js (5842:18) renderElement file:///Users/shertu/shertu/apple/node_modules/.pnpm/react-dom@18.2.0_react@18.2.0/node_modules/react-dom/cjs/react-dom-server.browser.development.js (6005:11) renderNodeDestructiveImpl file:///Users/shertu/shertu/apple/node_modules/.pnpm/react-dom@18.2.0_react@18.2.0/node_modules/react-dom/cjs/react-dom-server.browser.development.js (6104:11) renderNodeDestructive file:///Users/shertu/shertu/apple/node_modules/.pnpm/react-dom@18.2.0_react@18.2.0/node_modules/react-dom/cjs/react-dom-server.browser.development.js (6076:14) renderElement file:///Users/shertu/shertu/apple/node_modules/.pnpm/react-dom@18.2.0_react@18.2.0/node_modules/react-dom/cjs/react-dom-server.browser.development.js (5971:9) renderNodeDestructiveImpl file:///Users/shertu/shertu/apple/node_modules/.pnpm/react-dom@18.2.0_react@18.2.0/node_modules/react-dom/cjs/react-dom-server.browser.development.js (6104:11) renderNodeDestructive file:///Users/shertu/shertu/apple/node_modules/.pnpm/react-dom@18.2.0_react@18.2.0/node_modules/react-dom/cjs/react-dom-server.browser.development.js (6076:14)
两个仓库均由提问者自行维护,可任意调整两边配置。
根因定位
从调用栈的Styled(div)标识和registered字段报错判断,这是典型的Emotion CSS-in-JS库服务端渲染上下文缺失问题,触发原因通常是以下两种:
- blinq-image-editor构建时将react、@emotion系列依赖打包进了产物,导致npm包和Next.js应用各自持有一份独立的Emotion实例,服务端渲染时组件读取不到应用侧注入的样式上下文,拿到null值报错
- Next.js应用侧没有配置Emotion的服务端渲染逻辑,服务端执行渲染时没有初始化对应的样式上下文
解决步骤
1. 修复blinq-image-editor的构建配置
这是最核心的修复点,从根源解决多实例冲突问题:
- 将
react、react-dom、所有@emotion/*开头的依赖从package.json的dependencies字段移到peerDependencies,声明为宿主环境需要提供的公共依赖 - 在构建工具配置中把上述依赖标记为external,不要打包进最终dist产物。如果是用Vite构建库,参考配置如下:
// vite.config.js export default { build: { lib: { // 保留原有库构建配置 }, rollupOptions: { external: ['react', 'react-dom', '@emotion/react', '@emotion/styled', '@emotion/cache'], output: { globals: { react: 'React', 'react-dom': 'ReactDOM', '@emotion/react': 'emotionReact', '@emotion/styled': 'emotionStyled' } } } } }
- 重新构建并发布新版本的npm包,确认产物中不包含上述公共依赖的代码。
2. 配置Next.js应用的Emotion SSR支持
- 安装必要依赖:
npm i @emotion/cache @emotion/server @emotion/react @emotion/styled - 在pages目录下新建
_document.js,配置服务端样式提取逻辑,参考配置:
// pages/_document.js import Document, { Html, Head, Main, NextScript } from 'next/document' import createCache from '@emotion/cache' import createEmotionServer from '@emotion/server/create-instance' const cache = createCache({ key: 'css' }) const { extractCriticalToChunks } = createEmotionServer(cache) export default class MyDocument extends Document { render() { return ( <Html lang="zh-CN"> <Head> {this.props.styleTags} </Head> <body> <Main /> <NextScript /> </body> </Html> ) } } MyDocument.getInitialProps = async (ctx) => { const originalRenderPage = ctx.renderPage ctx.renderPage = () => originalRenderPage({ enhanceApp: (App) => (props) => <App emotionCache={cache} {...props} /> }) const initialProps = await Document.getInitialProps(ctx) const emotionStyles = extractCriticalToChunks(initialProps.html) const styleTags = emotionStyles.styles.map(style => ( <style data-emotion={`${style.key} ${style.ids.join(' ')}`} key={style.key} dangerouslySetInnerHTML={{ __html: style.css }} /> )) return { ...initialProps, styleTags: [...initialProps.styles, ...styleTags] } }
- 修改pages目录下的
_app.js,全局注入Emotion缓存:
// pages/_app.js import { CacheProvider } from '@emotion/react' import createCache from '@emotion/cache' const clientCache = createCache({ key: 'css' }) export default function MyApp({ Component, pageProps }) { return ( <CacheProvider value={clientCache}> <Component {...pageProps} /> </CacheProvider> ) }
- 安装更新后的blinq-image-editor包,重启Next.js开发服务即可正常运行。
3. 临时调试兜底方案
如果需要快速验证组件功能,暂时不想调整两边配置,可以直接关闭该组件的服务端渲染,用Next.js动态导入实现:
import dynamic from 'next/dynamic' // 导入时关闭ssr const BlinqImageEditor = dynamic(() => import('blinq-image-editor'), { ssr: false }) // 页面中直接使用组件即可,绕开SSR阶段的上下文问题
该方案会损失组件的SSR渲染能力,仅适合临时调试使用,长期使用建议按前两个步骤完成正式修复。
内容的提问来源于stack exchange,提问作者shertu
相关产品推荐
相关产品推荐

