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(包内的,描述依赖关系)缺失。重装重建整个目录树,比精确修复省时。
预防
- CI 加
npm ci而非npm install——ci严格按 lock 文件安装,不一致直接报错而非静默修复; - TypeScript 项目加
skipLibCheck之前先跑一次类型检查——排除第三方类型报错的干扰; - 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"(或直接打开文件),再怪包本身。
排查清单
ls node_modules/<包名>— 目录存在?- 检查路径是否以
./开头(相对路径) rm -rf node_modules && npm install— 重装修复- 大小写核对(本地→部署炸 = 大概率这个)
"type": "module"检查 — require vs import 是否匹配- 编译产物:
paths别名是否已重写,子路径是否在exports白名单内
排查注记
六种原因按概率排序,但第 3 条「删 node_modules 重装」单独解释了为什么能解决 80% 的问题:安装中断留下的半安装状态,靠重装重建整个目录树比精确修复省时——node_modules 本质是临时产物,重装无损。大小写那条标成「本地能跑、部署就炸」的经典,依据是 macOS 文件系统默认大小写不敏感而 Linux、容器、CI 敏感。清单之外的三个隐形病因给了同一句忠告:看栈底帧而不是报错首行——某些栈会把模块自己的语法错误也报成 Cannot find module。
感谢阅读这篇 ToolVault 工具匣 技术文章。相关工具:Linux 命令速查。相关阅读:npm 换源指南、Node 17+ 报 digital envelope routines 错误排查、npm ERESOLVE 依赖冲突。访问 首页 查看更多开发者工具。
相关工具
相关文章
Linux crontab 定时任务实战:命令、日志与排查
crontab 不只是写表达式:三个必会命令、/etc/cron.d 与用户 crontab 的区别、任务没跑的五步排查法、PATH/时区/% 转义、MAILTO,以及秒级任务的正确解法。
docker run 转 docker-compose 完全指南:为什么转、怎么转、参数对照表
把 docker run 命令迁移到 docker-compose.yml 的完整指南:compose 相比裸 run 的五大优势、参数逐项对照表(端口/卷/环境变量/重启策略/网络)、常见坑(裸 -e 透传、匿名卷、--rm 语义),附在线转换器用法。
Permission denied (publickey) 怎么解决?Git 推送失败的 6 种原因与修复
git clone/push 报 Permission denied (publickey) fatal: Could not read from remote repository?覆盖公钥没生成、没加载进 agent、没添加到 GitHub/Gitee、多账号配错密钥、deploy key 权限、remote URL 写错六种根因,附 ssh -v 诊断方法。