跳到主要内容
DevOps2026-09-085 分钟阅读

Cannot find module 报错怎么解决?Node.js 模块找不到的 6 种原因与修复

现象:Cannot find module 'xxx'

Error: Cannot find module 'express'
Require stack:
- /app/server.js

或 ESM 形态:

Error [ERR_MODULE_NOT_FOUND]: Cannot find package 'lodash' imported from /app/index.js

两句话诊断完毕:Node 找不到你要求加载的那个模块文件。可能文件不存在、路径不对、或者没安装。

六种原因,按概率排序

1. 没安装(新手最常见)

npm install express    # 装了就好

判断方法:ls node_modules/express 是否存在。

2. 路径写错(相对路径忘了 ./)

require('utils');       // ❌ Node 去 node_modules 找 utils 包
require('./utils');     // ✅ 相对路径必须以 ./ 或 ../ 开头

区别一句话:不以 ./ ../ / 开头的路径一律去 node_modules 里找。

3. node_modules 损坏(安装中断/磁盘满)

rm -rf node_modules package-lock.json
npm install

删除重装能解决大部分诡异问题——node_modules 本质是临时产物,重装无损。

4. 大小写不匹配(macOS 能跑、Linux 炸)

macOS 文件系统默认大小写不敏感,require('./Utils') 能找到 utils.js;但 Linux(包括 Docker 容器和 CI 服务器)大小写敏感——本地能跑部署就炸的经典原因。

# 检查文件名实际大小写
ls -la src/utils/ | grep -i util

5. ESM 和 CommonJS 混用

package.json 里加了 "type": "module" 后,.js 文件全部按 ESM 解析:

写法CommonJS (.js)ESM ("type":"module")
require('./x')✅❌ ERR_MODULE_NOT_FOUND
import x from './x.js'❌✅(必须带扩展名)

ESM 的两个关键差异:import 路径必须含文件扩展名(./utils.js 而非 ./utils),且不能省略 .js 后缀。

6. monorepo / workspace 路径问题

在 monorepo 中引用兄弟包时,确认 package.json 的 workspaces 配置包含了该包,且包名与 name 字段一致。

为什么删了 node_modules 重装能解决 80% 的问题

node_modules 的完整性依赖 npm install 一次成功执行到底。安装中途断网、Ctrl+C、磁盘满都可能留下半安装状态——文件夹存在但 package.json(包内的,描述依赖关系)缺失。重装重建整个目录树,比精确修复省时。

预防

  1. CI 加 npm ci 而非 npm install——ci 严格按 lock 文件安装,不一致直接报错而非静默修复;
  2. TypeScript 项目加 skipLibCheck 之前先跑一次类型检查——排除第三方类型报错的干扰;
  3. monorepo 用 workspace 协议("deps": {"@repo/utils": "workspace:*"})代替相对路径。

清单之外的三个隐形病因

(1)大小写漂移:macOS(大小写不敏感)上代码引用 ./utils/Logger 而文件是 logger.js——本地能跑、Linux CI/容器里死。一条命令的审计:tsc 的 forceConsistentCasingInFileNames 在 CI 之前抓住它。(2)路径里的异字符:项目目录含空格或非 ASCII 字符,部分打包器在 Windows 上的模块解析直接坏——报错显示 module not found 却不提路径是元凶。(3)模块本身在求值时抛错——某些栈里 require() 把内部错误也报成 Cannot find module(语法错误、模块自己的 import 失败)。看栈底帧而不是首行,三者立刻分开。

ESM 与 CJS:同一本修复手册说两种话

require() 隐式补 .js 扩展名、目录找 index.js;ES import 需要足够明确的说明符和 type: module 或 .mjs。CJS 式修复(往 package.json 加依赖)对 ESM 文件无效——现代包的 exports map 才是守门人,import 一个不在 map 里的子路径(pkg/lib/internal)会报 not found,尽管文件就在 node_modules 里。诊断分流两行命令:node -e "console.log(require.resolve('pkg'))"(CJS 世界)与动态 import()(ESM 世界)——哪个通,你就是哪个世界。

复发防治:两行测试文件钉进 CI

同样的 module 报错每月回来一次,说明上次修的是症状。耐用的模板:写一个两行测试文件(import 该模块、打印 ok)——测试文件能跑而应用不能跑,问题在配置(paths、tsconfig baseUrl、打包器 alias)不在模块。把这个测试钉进 CI;下次漂移时是一个 5 秒就红的检查,而不是一个下午的调试。

TypeScript paths 与运行时解析不是一回事

tsconfig.json 的 paths / baseUrl 只教编译器(以及部分打包器)去哪找模块。Node 运行时不会读这些别名,除非你加了 loader(tsx、ts-node、module-alias)或在构建时把别名展开。经典翻车:import '@/lib/x' 在 IDE 里类型检查通过,node dist/server.js 却报 ERR_MODULE_NOT_FOUND——因为产物 JS 里还留着 @/。要么构建期重写别名,要么运行时改用相对路径 / package exports。调试时先打开编译后的文件:别名还在,问题就在构建管线,不在 npm。

条件 exports 与双包

现代包用 exports 映射,并按 import / require / default 条件分流。导入未列入的子路径会报「找不到」,尽管文件就在 node_modules 里。双 CJS/ESM 包还可能按条件暴露不同入口;从 CJS 去 require 仅 ESM 的入口(或反过来)会表现为 module-not-found 或 ERR_REQUIRE_ESM。先看 node -p "require('pkg/package.json').exports"(或直接打开文件),再怪包本身。

排查清单

  1. ls node_modules/<包名> — 目录存在?
  2. 检查路径是否以 ./ 开头(相对路径)
  3. rm -rf node_modules && npm install — 重装修复
  4. 大小写核对(本地→部署炸 = 大概率这个)
  5. "type": "module" 检查 — require vs import 是否匹配
  6. 编译产物:paths 别名是否已重写,子路径是否在 exports 白名单内

排查注记

六种原因按概率排序,但第 3 条「删 node_modules 重装」单独解释了为什么能解决 80% 的问题:安装中断留下的半安装状态,靠重装重建整个目录树比精确修复省时——node_modules 本质是临时产物,重装无损。大小写那条标成「本地能跑、部署就炸」的经典,依据是 macOS 文件系统默认大小写不敏感而 Linux、容器、CI 敏感。清单之外的三个隐形病因给了同一句忠告:看栈底帧而不是报错首行——某些栈会把模块自己的语法错误也报成 Cannot find module。


感谢阅读这篇 ToolVault 工具匣 技术文章。相关工具:Linux 命令速查。相关阅读:npm 换源指南、Node 17+ 报 digital envelope routines 错误排查、npm ERESOLVE 依赖冲突。访问 首页 查看更多开发者工具。