在服务端渲染(SSR)中解决 JavaScript 代码在 Node.js 和浏览器环境下的差异,核心是通过环境判断、模块适配、配置隔离和工具链支持实现同构代码。以下是具体解决方案:
1. 避免直接使用浏览器专有对象Node.js 环境缺乏 window、document、navigator 等浏览器全局对象,直接访问会导致服务端报错。需通过环境判断隔离逻辑:
- 条件判断:在使用前检查对象是否存在,例如:if (typeof window !== 'undefined') { // 浏览器端逻辑(如 DOM 操作)}
- 延迟执行:将依赖 DOM 的操作(如事件监听、动画)延迟到组件挂载后执行。例如:
React:使用 useEffect 钩子。
Vue:使用 mounted 生命周期钩子。
- 条件引入第三方库:对仅支持浏览器的库(如某些动画库),通过动态导入或环境判断避免服务端执行:let BrowserOnlyLib;if (typeof window !== 'undefined') { BrowserOnlyLib = require('browser-only-lib');}
2. 统一模块系统语法Node.js 默认使用 CommonJS(require/module.exports),而浏览器端通常使用 ES Modules(import/export)。需通过以下方式兼容:
- 统一使用 ES Modules:在项目中统一采用 import/export 语法,由构建工具(如 Webpack、Vite)自动转换为 CommonJS 格式。
- 原生 ESM 支持:若服务端使用原生 ESM,需在 package.json 中设置 "type": "module",并确保所有依赖支持 ESM。
- 动态导入:对浏览器专用模块(如某些 UI 库)使用动态导入(import()),避免服务端加载无关代码:const BrowserModule = typeof window !== 'undefined' ? await import('browser-module') : null;
3. 管理环境变量与配置不同环境(开发、生产、测试)可能需要不同的 API 地址、功能开关或敏感信息。需通过以下方式隔离配置:
- 构建时注入环境变量:
使用 process.env.NODE_ENV 区分环境。
通过构建工具插件(如 Vite 的 define 或 Webpack 的 DefinePlugin)将变量编译进代码:// vite.config.jsexport default defineConfig({ define: { 'process.env.API_URL': JSON.stringify(process.env.API_URL), },});
- 避免泄露敏感信息:服务端仅传递必要的客户端配置,敏感信息(如数据库密码)应通过环境变量或密钥管理服务获取,而非硬编码在代码中。
4. 统一数据获取方式浏览器端通常使用 fetch,而 Node.js 需通过 node-fetch、axios 或自定义封装实现跨平台请求:
5. 借助工具链抹平差异构建工具(如 Webpack、Vite、Rollup)和框架(如 Next.js、Nuxt.js)提供了内置支持,可自动处理环境差异:
- Next.js:内置 SSR 支持,自动隔离浏览器/服务端代码,提供 getServerSideProps 和 getStaticProps 预取数据。
- Vite:通过 @vitejs/plugin-react 等插件优化 SSR 构建,支持环境变量注入和模块热更新。
- Babel 插件:使用 @babel/plugin-transform-modules-commonjs 等插件转换模块语法。
关键原则- 共享逻辑,隔离副作用:将业务逻辑(如状态管理、API 调用)写为同构代码,将环境相关操作(如 DOM 操作、存储访问)隔离到特定生命周期或条件分支中。
- 控制边界:明确代码在服务端和浏览器的执行范围,避免服务端执行浏览器专属逻辑(如路由跳转、动画播放)。
- 测试覆盖:通过单元测试和端到端测试验证代码在两端的兼容性。
通过以上方法,可实现 JavaScript 代码在 Node.js 和浏览器环境下的无缝运行,充分发挥 SSR 的性能优势(如首屏加载优化、SEO 支持)。