在React Router中,如何在客户端和服务端导入同一个接口的不同实现?

前端开发 2026-07-11

我正在替换一个大型React Router应用中的日志系统,面临的挑战是我想根据代码是在服务器端还是客户端运行来使用不同的日志实现,但希望日志实现遵循同一个接口,以便无论是在应用的哪一端,我们都可以调用 logger.warnlogger.information,让系统把日志路由到正确的位置。

问题在于服务器端的日志使用的是Node特定的库,在客户端不可用。我设计了一些逻辑,使用条件加载来导入服务器端的代码,如下所示:

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

let logger: ILogger = consoleLogger;  
if (typeof window === "undefined") {  
     (async () => {
        const serverSideLogs = await import("~/util/nodeLogger.server");
         logger = serverSideLogs.nodeLogger;  
     });
}

其中 consoleLoggernodeLogger 都是 ILogger 接口的实现。

在本地构建时能正确构建,但推送到构建服务器后会失败,错误信息如下:

[commonjs--resolver] Server-only module referenced by client

从技术角度讲,这句话是正确的,因为引用位于客户端引用的文件中,但它也是错误的,因为该导入在客户端永远不应该被调用(如果被调用就会失败)。

如何确保始终有一个 ILogger 可用,但在客户端或服务器端使用正确的那个?

解决方案

你可以通过对现有的两种策略进行一些重构来定义一个Vite插件:

nodeLogger.server.tsconsoleLogger.ts 中都添加一个 logger 导出:

export { nodeLogger as logger };
export { consoleLogger as logger };

~/util/logger.ts 中你可以简单地声明如下:

import type { ILogger } from "~/types/logger";
export declare const logger: ILogger;

最后在你的 vite.config.ts 中添加一个自定义插件,使用 ssr option 来支持导入 ~/util/logger 的使用者:

function loggerPlugin(): Plugin {
  return {
    name: "logger-resolver",
    resolveId(id, _importer, options) {
      if (id === "~/util/logger") {
        return options.ssr
          ? this.resolve("~/util/nodeLogger.server")
          : this.resolve("~/util/consoleLogger");
      }
    },
  };
}

export default defineConfig({ plugins: [loggerPlugin()] });
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章