使用React+Emotion+Vite构建的UI组件包在Next.js应用中调用时出现服务器错误的解决方案求助
我帮你拆解下这个问题——你碰到的TypeError: Cannot read properties of null (reading 'registered'),本质是Emotion的缓存对象在Next.js服务器端渲染(SSR)时没被正确初始化,哪怕加了<CacheProvider>也没起效。这通常是因为组件包的Emotion配置和Next.js的SSR上下文没匹配上,下面是针对性的解决方法:
1. 在Next.js端配置SSR兼容的CacheProvider
针对Next.js 13+ App Router
在根layout.tsx里,要给每个服务器请求单独创建Emotion缓存(避免跨请求污染),并确保CacheProvider包裹整个应用:
import { CacheProvider } from '@emotion/react'; import createCache from '@emotion/cache'; import type { Metadata } from 'next'; // 每个请求创建独立缓存 function createEmotionCache() { return createCache({ key: 'next-ui-emotion', prepend: true, // 保证样式优先级正确 }); } export const metadata: Metadata = { title: '你的应用', description: '使用Emotion UI组件包的应用', }; export default function RootLayout({ children, }: { children: React.ReactNode; }) { const cache = createEmotionCache(); // 开启SSR兼容模式,这一步很关键 cache.compat = true; return ( <html lang="zh-CN"> <body> <CacheProvider value={cache}>{children}</CacheProvider> </body> </html> ); }
针对Next.js Pages Router
在_app.tsx中做类似配置:
import { CacheProvider } from '@emotion/react'; import createCache from '@emotion/cache'; import type { AppProps } from 'next/app'; // 创建全局缓存(Pages Router下可复用) const cache = createCache({ key: 'css', prepend: true }); export default function App({ Component, pageProps }: AppProps) { return ( <CacheProvider value={cache}> <Component {...pageProps} /> </CacheProvider> ); }
2. 检查UI组件包的依赖与Vite配置
从你给出的vite.config.js来看,核心配置没问题,但要确认两点:
组件包的
package.json必须包含正确的peerDependencies:
确保这些依赖被标记为peer依赖,让Next.js应用来提供,避免组件包打包时把它们塞进产物里导致缓存冲突:"peerDependencies": { "@emotion/react": "^11.10.0", "@emotion/styled": "^11.10.0", "react": "^17.0.0 || ^18.0.0", "react-dom": "^17.0.0 || ^18.0.0" }你的Vite配置里已经用
Object.keys(packageJson.peerDependencies)作为external,只要peerDependencies配置正确,这一步就没问题。组件包内的组件必须正确使用Emotion API:
所有样式都要通过@emotion/styled或者@emotion/react的cssprop创建,不能直接操作Emotion的全局缓存对象——这会导致脱离上下文,在SSR时找不到缓存。
3. 可选:服务器端样式提取(推荐)
为了确保SSR时样式正确注入到HTML中,避免页面闪烁,可以结合@emotion/server的extractCritical函数提取关键样式:
App Router示例(需用服务器组件)
import { CacheProvider } from '@emotion/react'; import createCache from '@emotion/cache'; import { extractCritical } from '@emotion/server'; import { renderToString } from 'react-dom/server'; function createEmotionCache() { return createCache({ key: 'css', prepend: true }); } export default function RootLayout({ children }) { const cache = createEmotionCache(); cache.compat = true; // 服务器端渲染并提取关键样式 const html = renderToString(<CacheProvider value={cache}>{children}</CacheProvider>); const { css, ids } = extractCritical(html); return ( <html lang="zh-CN"> <head> {/* 将提取的样式注入到页面头部 */} <style data-emotion={`css ${ids.join(' ')}`} dangerouslySetInnerHTML={{ __html: css }} /> </head> <body> <CacheProvider value={cache}>{children}</CacheProvider> </body> </html> ); }
4. 验证组件包的打包产物
构建组件包后,检查lib目录下的产物代码,确保@emotion/react、@emotion/styled这些依赖的导入还是原样,没有被打包进组件包——只有让Next.js应用来解析这些依赖,缓存上下文才能正确传递。
做完这些步骤后,服务器端的Emotion缓存应该就能正确初始化,不会再出现null访问的错误了。
内容的提问来源于stack exchange,提问作者user19368552

