Module Federation SSR用React.lazy加载模块报useState空值错误求助
问题:React SSR中使用Module Federation时服务端报错"Cannot read properties of null (reading 'useState')"
现象
客户端通过React.lazy加载联邦模块完全正常,但服务端渲染时抛出错误:Cannot read properties of null (reading 'useState'),切换为UniversalFederationPlugin后问题依旧。
环境与代码
- 依赖版本:
"@module-federation/node": "^0.9.11", "react": "^18.2.0", "webpack": "^5.74.0"
- 模块导入方式:
const Homepage = React.lazy(() => import('homePage/Homepage')); // 路由配置 {path: '/overview', element: <Homepage />},
- 使用自定义React SSR服务。
相关配置
1. Shell应用客户端Webpack配置
{ name: 'client', target: 'web', entry: { client: path.resolve(__dirname, '../../src/client/index.ts'), }, output: { path: path.resolve(__dirname, '../../dist/client'), filename: 'js/[name].[contenthash].js', publicPath: '/static/', clean: true, assetModuleFilename: 'images/[hash][ext]', }, optimization: { moduleIds: 'deterministic', minimize: !isDev ? true : false, minimizer: !isDev ? [new TerserPlugin()] : [], }, module: { rules: [ ...rules, { test: /\.s[ac]ss$/i, use: [ MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader', ], }, ], }, plugins: [ ...plugins.client, ], };
2. Shell应用Webpack插件配置
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin'); const {NodeFederationPlugin, StreamingTargetPlugin} = require('@module-federation/node'); const path = require('path'); const dependencies = require(path.join(__dirname, '../../../package.json')).dependencies; const remotes = { server: { homePage: 'homePage@http://localhost:8080/server/homePageRemote.js', }, client: { homePage: 'homePage@http://localhost:8080/client/homePageRemote.js', }, }; module.exports = { client: [ new ModuleFederationPlugin({ name: 'shellApp', remotes: {...remotes.client}, shared: { react: { singleton: true, requiredVersion: dependencies['react'], }, 'react-dom': { singleton: true, requiredVersion: dependencies['react-dom'], }, }, }), ], server: [ new NodeFederationPlugin({ name: 'shellApp', library: {type: 'commonjs-module'}, remotes: {...remotes.server}, shared: { react: { singleton: true, requiredVersion: dependencies['react'], }, 'react-dom': { singleton: true, requiredVersion: dependencies['react-dom'], }, }, }), new StreamingTargetPlugin({ name: 'shellApp', library: { type: 'commonjs-module' }, remotes: {...remotes.server}, }), ], };
3. 远程应用服务端Webpack配置
const path = require('path'); const mainRules = require('../additionally/rules'); const { NodeFederationPlugin, StreamingTargetPlugin } = require('@module-federation/node'); const extensions = require('../additionally/extensions'); const mode = process.env.NODE_ENV === 'development' ? 'development' : 'production'; const isDev = mode === 'development'; const dependencies = require('../../package.json').dependencies; module.exports = { entry: { server: path.join(__dirname, '../../src/index.tsx') }, stats: { colors: true, entrypoints: false, children: false, }, target: false, output: { path: path.join(__dirname, '../../dist/server'), filename: '[name].[contenthash].js', clean: true, publicPath: 'http://localhost:8080/server', }, resolve: { extensions, }, devtool: isDev ? 'inline-source-map' : 'source-map', module: { rules: [ ...mainRules, { test: /\.(css|scss)$/i, use: [ { loader: "css-loader", }, { loader: "sass-loader", }, ], exclude: /node_modules/ } ], }, plugins: [ new NodeFederationPlugin({ name: 'homePage', filename: 'homePageRemote.js', library: { type: "commonjs-module" }, remotes: {}, exposes: { './Homepage': path.resolve(__dirname, '../../src/Homepage.tsx'), }, shared: { react: { singleton: true, requiredVersion: dependencies['react'], }, 'react-dom': { singleton: true, requiredVersion: dependencies['react-dom'], } } }), new StreamingTargetPlugin({ name: 'homePage', library: { type: "commonjs-module" }, remotes: {}, shared: { react: { singleton: true, requiredVersion: dependencies['react'], }, 'react-dom': { singleton: true, requiredVersion: dependencies['react-dom'], } } }) ] }
疑问
为什么React依赖没有被正确共享?
解答
这个错误本质是服务端渲染时React实例不共享,导致useState等钩子调用时找不到正确的React上下文。以下是核心原因和解决办法:
1. 核心原因
React.lazy不支持服务端渲染:React.lazy是客户端专用的懒加载方案,服务端无法处理异步加载的组件,会导致组件渲染时React上下文未正确初始化,出现null引用错误。- 服务端联邦模块的共享逻辑未完全生效:虽然配置了
singleton: true,但服务端加载联邦模块时,可能因为模块解析顺序、Webpack配置细节问题,导致远程模块加载了自己的React副本,而非共享Shell的React实例。
2. 解决步骤
步骤1:替换React.lazy为服务端兼容的异步加载方案
使用React.Suspense配合服务端支持的组件加载方式,或者直接同步导入联邦模块(如果不需要懒加载):
// 替换React.lazy为同步导入(服务端友好) import Homepage from 'homePage/Homepage'; // 路由配置 {path: '/overview', element: <Suspense fallback={<div>Loading...</div>}><Homepage /></Suspense>},
如果必须懒加载,可以使用@loadable/component替代React.lazy,它专门支持SSR场景:
import loadable from '@loadable/component'; const Homepage = loadable(() => import('homePage/Homepage'), { fallback: <div>Loading...</div>, });
步骤2:确保服务端联邦配置的一致性
- 检查
shared配置的eager属性:在服务端的shared配置中添加eager: true,确保React在联邦模块加载前就被初始化,避免远程模块加载自己的副本:
// Shell服务端NodeFederationPlugin的shared配置 shared: { react: { singleton: true, requiredVersion: dependencies['react'], eager: true, // 添加此属性 }, 'react-dom': { singleton: true, requiredVersion: dependencies['react-dom'], eager: true, // 添加此属性 }, },
- 远程应用服务端配置同步:远程应用的
shared配置也需要添加eager: true,确保它优先使用Shell提供的React实例。
步骤3:验证服务端模块解析逻辑
- 确保服务端Webpack配置的
target设置正确(当前设置target: false在@module-federation/node中是允许的,但要确保没有其他冲突配置)。 - 检查服务端入口文件是否正确初始化React上下文,确保在渲染联邦组件前,React已经被正确加载。
步骤4:升级依赖版本
@module-federation/node的旧版本存在一些SSR共享依赖的bug,尝试升级到最新稳定版,可能会解决此类兼容性问题。
内容的提问来源于stack exchange,提问作者user469485
相关产品推荐
相关产品推荐

