You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Vite构建的React UI组件库在Next.js 14 SSR环境下异常求助

问题描述
  • 用Vite构建的React UI组件库(含20+组件)在客户端渲染(CSR)应用中运行正常,但在Next.js 14中使用时出现以下问题:
    1. 初始报错:

      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

    2. 移除其他组件仅保留按钮后,组件可渲染但无CSS样式;添加'use client'指令后样式恢复,但希望组件能像Material UI的按钮一样,在服务端渲染(SSR)时也加载样式
    3. 已尝试在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:

  1. 给组件库入口文件添加'use client'指令
    在组件库根目录的index.ts顶部添加:

    'use client';
    export * from './src/components';
    

    这会将整个组件库标记为客户端组件,让Next.js在客户端正确加载,避免RSC环境调用客户端API的报错。

  2. 优化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全局导入

  1. 移除vite-plugin-css-injected-by-js插件
    该插件不适合SSR场景,改用Vite自带的CSS提取功能,在build配置中添加:

    build: {
      // ...其他配置
      cssCodeSplit: true,
      rollupOptions: {
        output: {
          // 确保CSS文件正确输出
          assetFileNames: 'assets/[name].[hash][extname]',
        },
      },
    }
    

    此时Vite会将CSS提取为单独文件,而非注入JS。

  2. 在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为例:

  1. 安装依赖

    npm install @emotion/react
    npm install --save-dev @emotion/babel-plugin
    
  2. 修改Vite的React插件配置
    让Vite支持emotion的babel插件:

    plugins: [
      react({
        babel: {
          plugins: ['@emotion/babel-plugin'],
        },
      }),
      tsConfigPaths()
      // 移除cssInjectedByJsPlugin
    ],
    
  3. 在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体验。

三、其他优化点

  1. 避免客户端专属API的SSR冲突
    组件中若需使用window、document等仅客户端可用的API,要放在useEffect或客户端生命周期钩子中执行:

    useEffect(() => {
      // 客户端专属逻辑,比如操作DOM或window对象
    }, []);
    
  2. 测试SSR兼容性
    搭建Next.js测试页面,验证组件在SSR渲染时无报错、样式正常加载。

内容的提问来源于stack exchange,提问作者Himanshu Sain

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 23:10:11