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

React Router应用中如何在客户端与服务端导入接口的不同实现,适配跨端统一接口的日志系统

解决React Router SSR应用中跨环境Logger的构建报错与竞态问题

首先得说,你当前的代码有两个关键问题:一是构建工具的静态分析会误判服务端模块的引用,二是异步导入会导致服务端初始化阶段Logger实例不正确的竞态问题。咱们一步步解决:

为什么会出现构建报错?

大多数现代构建工具(比如Vite、Webpack、Next.js打包器)都是静态分析代码的——它们会扫描文件里所有的导入语句,不管你用了什么条件判断包裹。哪怕你写了typeof window === "undefined",构建工具还是会看到文件里有import("~/util/nodeLogger.server")这个引用,从而判定客户端代码引用了服务端专属模块,触发报错。

另外,你用了异步自执行函数来替换Logger,这会导致服务端启动时,Logger一开始还是consoleLogger,直到异步导入完成才会换成nodeLogger——如果这期间有日志调用,就会错误地输出到控制台,而不是你期望的服务端日志系统。

解决方案1:同步条件导入 + 构建工具可识别的环境标识

把异步导入换成同步的,并使用构建工具能静态分析的环境变量(比如Vite的import.meta.env.SSR,Next.js的process.env.NEXT_RUNTIME),让构建工具在打包客户端时自动剔除服务端分支的代码:

针对Vite环境:

import { type ILogger } from "~/types/logger";
import { consoleLogger } from "~/util/consoleLogger";

const logger: ILogger = import.meta.env.SSR 
  ? require("~/util/nodeLogger.server").nodeLogger 
  : consoleLogger;

针对Next.js环境:

import { type ILogger } from "~/types/logger";
import { consoleLogger } from "~/util/consoleLogger";

const logger: ILogger = process.env.NEXT_RUNTIME === "nodejs"
  ? require("~/util/nodeLogger.server").nodeLogger 
  : consoleLogger;

这里用require而不是静态import,是为了让构建工具更明确地识别这是服务端专属的引用;同时,框架提供的环境变量是静态可分析的,打包客户端时会直接删掉服务端分支的代码,不会再触发报错。

解决方案2:使用工厂函数封装初始化逻辑

如果不想直接用环境变量,可以写一个工厂函数,在应用启动时同步创建正确的Logger实例,确保没有竞态问题:

// ~/util/loggerFactory.ts
import { type ILogger } from "~/types/logger";
import { consoleLogger } from "~/util/consoleLogger";

export function getLogger(): ILogger {
  if (typeof window === "undefined") {
    // 服务端环境下同步加载服务端Logger
    const { nodeLogger } = require("~/util/nodeLogger.server");
    return nodeLogger;
  }
  // 客户端直接返回consoleLogger
  return consoleLogger;
}

然后在应用入口或者需要Logger的地方调用:

import { getLogger } from "~/util/loggerFactory";

const logger = getLogger();
// 之后就可以正常调用logger.warn/logger.information了

这个方案的好处是把Logger的初始化逻辑封装起来,代码更清晰,同时避免了异步竞态问题。

解决方案3:利用ES模块条件导出(进阶方案)

如果你的项目支持ES模块的条件导出,可以在package.json里定义一个Logger模块的环境专属导出,让构建工具自动根据环境选择实现:

在项目根目录的package.json里添加:

{
  "exports": {
    "./logger": {
      "node": "./src/util/nodeLogger.server.ts",
      "browser": "./src/util/consoleLogger.ts"
    }
  }
}

然后直接在代码里导入:

import { logger } from "~/logger";

这种方式最简洁,构建工具会自动帮你处理环境分支,完全不需要在代码里写条件判断。

关键注意事项

  • 永远不要在客户端代码里出现服务端模块的静态import语句,哪怕是在条件分支里——构建工具的静态分析会直接检测到。
  • 尽量同步初始化Logger,避免异步导入带来的竞态问题,确保应用启动时就有正确的Logger实例可用。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.27 10:22:35