基于Vite构建的React UI组件库在Next.js 14 SSR环境下异常求助
- 用Vite构建的React UI组件库(含20+组件)在客户端渲染(CSR)应用中运行正常,但在Next.js 14中使用时出现以下问题:
- 初始报错:
TypeError: (0, react_WEBPACK IMPORTED MODULE_0_._• createContext) is not a function at eval (webpack-internal:///(rsc)/-/node_modules/ui-library/dist/bundle.esm.js:2130:78) at (rsc)/./node modules/ui-library/dist/bundle.esm.js (/Users/himansh/Documents/ssr-uilib-test/ next/serve
- 移除其他组件仅保留按钮后,组件可渲染但无CSS样式;添加
'use client'指令后样式恢复,但希望组件能像Material UI的按钮一样,在服务端渲染(SSR)时也加载样式 - 已尝试在Vite配置中添加
ssr:true但未解决问题
- 初始报错:
当前Vite配置:
import { defineConfig } from "vite"; import tsConfigPaths from "vite-tsconfig-paths"; import react from "@vitejs/plugin-react"; import path, { resolve } from "path"; import cssInjectedByJsPlugin from "vite-plugin-css-injected-by-js"; export default defineConfig({ plugins: [react(), cssInjectedByJsPlugin(), tsConfigPaths()], resolve: { alias: { "@Components": path.resolve(__dirname, "src"), "@Utils": path.resolve(__dirname, "utils"), "@CustomHooks": path.resolve(__dirname, "utils/hooks"), "@Assets": path.resolve(__dirname, "assets"), "@Styles": path.resolve(__dirname, "styles"), // Add more aliases as needed }, }, css: { preprocessorOptions: { less: { // Less options (e.g., modifying variables) javascriptEnabled: true, }, }, }, build: { lib: { entry: resolve(__dirname, "./index.ts"), name: (format) => `bundle.${format}`, fileName: (format) => `bundle.${format}.js`, formats: ["esm"], }, outDir: resolve(__dirname, "../../dist"), rollupOptions: { // Externalize dependencies that are not to be bundled // input: resolve(__dirname, "./index.ts"), external: ["react", "react-dom"], // React and ReactDOM should be external output: { globals: { react: "React", "react-dom": "ReactDOM", }, }, }, }, });
一、修复React Context在RSC中的报错
Next.js 14默认启用React Server Components(RSC),组件库中使用的createContext属于客户端API,在Server Component环境中运行会触发错误。需要确保组件库代码仅在客户端执行,同时兼容SSR:
给组件库入口文件添加
'use client'指令
在组件库根目录的index.ts顶部添加:'use client'; export * from './src/components';这会将整个组件库标记为客户端组件,让Next.js在客户端正确加载,避免RSC环境调用客户端API的报错。
优化Vite外部依赖配置
确保React相关依赖在SSR和CSR环境中都能正确解析,修改rollupOptions.external:external: ['react', 'react-dom', 'react/jsx-runtime'],添加
react/jsx-runtime,避免打包时将其纳入产物,确保Next.js能正确提供该依赖。
二、实现SSR时的样式加载
当前使用的vite-plugin-css-injected-by-js会把CSS嵌入JS,这种方式在SSR时无法在服务端渲染样式(需客户端执行JS才会注入CSS)。要实现类似Material UI的SSR样式支持,可调整CSS处理方式:
方案1:CSS提取 + Next.js全局导入
移除
vite-plugin-css-injected-by-js插件
该插件不适合SSR场景,改用Vite自带的CSS提取功能,在build配置中添加:build: { // ...其他配置 cssCodeSplit: true, rollupOptions: { output: { // 确保CSS文件正确输出 assetFileNames: 'assets/[name].[hash][extname]', }, }, }此时Vite会将CSS提取为单独文件,而非注入JS。
在Next.js项目全局导入组件库CSS
在Next.js的app/layout.tsx(App Router)或pages/_app.tsx(Pages Router)中导入组件库输出的CSS文件:import 'ui-library/dist/assets/style.css'; // 根据实际输出路径调整这样SSR时Next.js会将CSS注入到HTML的
<head>中,实现服务端样式渲染。
方案2:CSS-in-JS方案(推荐,对标Material UI)
如果需要动态样式、主题切换等灵活能力,可采用CSS-in-JS方案,以emotion为例:
安装依赖
npm install @emotion/react npm install --save-dev @emotion/babel-plugin修改Vite的React插件配置
让Vite支持emotion的babel插件:plugins: [ react({ babel: { plugins: ['@emotion/babel-plugin'], }, }), tsConfigPaths() // 移除cssInjectedByJsPlugin ],在Next.js中配置emotion的SSR支持
在app/layout.tsx中添加emotion缓存提供器:'use client'; import { CacheProvider } from '@emotion/react'; import createCache from '@emotion/cache'; const clientSideCache = createCache({ key: 'css', prepend: true }); export default function RootLayout({ children }) { return ( <html lang="en"> <body> <CacheProvider value={clientSideCache}>{children}</CacheProvider> </body> </html> ); }这种方式下,样式会在SSR时被提取并注入HTML,客户端渲染时复用缓存,实现和Material UI一致的SSR体验。
三、其他优化点
避免客户端专属API的SSR冲突
组件中若需使用window、document等仅客户端可用的API,要放在useEffect或客户端生命周期钩子中执行:useEffect(() => { // 客户端专属逻辑,比如操作DOM或window对象 }, []);测试SSR兼容性
搭建Next.js测试页面,验证组件在SSR渲染时无报错、样式正常加载。
内容的提问来源于stack exchange,提问作者Himanshu Sain

