
不知道你有没有过这种体验后端接口写到一半每次改一个字段名就得回到终端按 CtrlC再重新 node app.js等着重启、看日志、再测试。一天下来这个动作重复几十次手都快练出肌肉记忆了。我第一次接触 nodemon 的时候最大的感受不是“这个工具好强”而是“我怎么早没装上它”。作为 Node 生态里存在了十几年的文件监听工具nodemon 至今仍是大多数 Node 项目本地开发的事实标准。它解决的核心问题很朴素监听源文件变化一旦检测到改动就自动重启服务。但“用起来简单”和“配置起来没坑”是两回事——很多人第一次配 nodemon 就卡在配置文件不生效、循环重启、监听不到文件这些“灵异事件”上。这篇文章把我从 1.x 用到 3.x 攒下的 nodemon 配置经验、踩坑记录和排查思路一次性说清楚适合刚接触 Node 的初学者也适合被配置文件折磨过的老手对照自查。1. 为什么要给 Node 开发配一个自动重启工具从手动重启说到选型1.1 自动重启解决的不只是“省一次按键”很多人觉得 nodemon 省下的不过是一句 CtrlC 再方向键上、回车好像也没多大事。但实际开发中真正折磨人的不是按键本身而是重启带来的“状态丢失”。你启动一个 Node 服务后内存里往往存着不少东西临时 token、缓存数据、数据库连接池、内存里的定时任务。手动重启之后这些全部重建如果代码里还有初始化顺序依赖第一遍启动可能还得等好几秒甚至十几秒。更烦的是改到一半忘了重启——前端同事拿着旧接口联调排查了半天才发现服务根本没起来浪费时间还伤和气。nodemon 把这些过程自动化并且保证“每次改动后都以同一套方式重启”不会出现你手动重启时漏掉某个参数、换了个环境变量的情况。它帮你在开发循环里去掉“进程管理”这部分脑力负担让你把注意力留在代码逻辑本身。这一点在写脚本、调接口、做定时任务时感受尤其明显。另外Node 进程本身是没有“改完自动生效”机制的PHP 有 FastCGI 常驻所以天然接近即时生效但 Node 服务通常是一个进程跑到底。nodemon 做的事情本质上就是监听—发现变化—杀掉旧进程—拉起新进程。它的成熟之处在于处理了一堆边界情况多次变化合并、子进程干净退出、特殊信号处理、监听目录过滤等等。1.2 和 Node 原生 --watch 的对比谁更值得用Node 从 18 版本开始提供了实验性的--watch参数到了 Node 20 以后逐渐完善。那我为什么还要选 nodemon直接用node --watch app.js不更省事吗我的判断标准从下面几个维度来看能力nodemon3.xnode --watch监听范围控制watch 数组精确指定ignore 规则完善只能监听入口模块依赖树比较粗扩展名过滤ext 可配比如只监听 js,ts,json只认依赖链上的文件不支持自定义扩展名重启延迟delay 可配避免频繁保存引起的连点重启无内置延迟高频保存容易连续重启自定义执行命令exec 可换成 ts-node、babel-node、python 等固定 node灵活性差生命周期事件events 可配置 restart、crash、exit 钩子无配置文件nodemon.json 或 package.json随项目走只能靠命令行参数平台稳定性Linux / macOS / Windows 多年的兼容积累各版本行为差异较大我在实际项目里经常遇到的情况是写 API 时改了配置文件.env或者.jsonnode --watch根本不认这些非模块文件而 nodemon 配一个ext: js,json就能覆盖到。这种情况下原生--watch就不够用了。但也不是说原生方案一无是处。如果你只是临时跑一个一次性脚本、或者演示某个纯 Node 特性、不想给项目增加任何依赖用node --watch完全可以。团队项目则建议统一用 nodemon因为nodemon.json是可以跟代码一起提交的新同事克隆下来跑npm install就自动获得一致的监听和重启行为这个“配置即基础设施”的价值在协作中很关键。2. 安装与初始化最容易踩的三层坑全局装、镜像源和 nodemon.json 生成2.1 全局安装 vs 本地安装团队项目为什么必须用后者很多人第一次装 nodemon 直接执行npm install -g nodemon装完在终端敲nodemon app.js确实能跑但问题出在项目协作上。假如你在package.json里写了scripts: { dev: nodemon app.js }团队里另一个人 clone 项目后执行npm install他本地并没有全局安装 nodemon这时npm run dev大概率会报nodemon: command not found。CI 环境也一样流水线里不可能替你全局安装开发工具。所以正确的做法是作为开发依赖安装npm install --save-dev nodemon本地安装后npm 会自动把node_modules/.bin目录加入 PATH所以 scripts 里写nodemon就能正确找到可执行文件。这里顺便解答一个高频疑问本地安装后为什么在终端直接敲nodemon会提示找不到命令原因在于终端当前 shell 的 PATH 里并没有node_modules/.bin。npm 只在执行 scripts 脚本时才临时把该目录加入 PATH你直接在命令行敲命令并不会。如果你就是想手动跑用npx nodemon app.js就好npx会去本地node_modules/.bin里找找不到再提示安装。我见过不少新手卡在这一步口诀记住团队项目永远装本地临时演示才用全局。2.2 安装超时和镜像源避免“半截安装”的恶性循环在大陆开发者环境里直接走官方源registry.npmjs.org安装依赖偶尔会遇到网络抖动导致安装超时。很多人会去把全局 registry 改成镜像源但遇到半截安装失败后又强行重装结果 node_modules 里残留一堆损坏的包。我更推荐的顺序是这样npm config set registry https://registry.npmmirror.com rm -rf node_modules package-lock.json npm install先统一 registry再清掉可能不完整的依赖树最后重装。为什么要删package-lock.json因为 lockfile 里的resolved字段记录的是旧源的下载地址如果你改源之后不删它npm 还是会优先尝试旧地址改了等于没完全改。这里有个注意事项如果你所在公司有自建 npm 私服统一用公司内网源不要自己乱改公共源否则拉取公司私有包时会报 404 或权限错误。开发机上改用户级配置即可不要动全局配置去影响别的项目。改完源之后装 nodemon 一般就是几秒的事npm install --save-dev nodemon安装了之后可以执行npx nodemon --version验证版本能正常打印版本号就说明环境这块通了。2.3 用 nodemon --init 生成一份兜底配置如果你不确定配置项怎么写官方提供了一个初始化命令npx nodemon --init它会在当前目录生成一个nodemon.json内容大概是这样的{ $schema: https://json.schemastore.org/nodemon.json, restartable: rs, ignore: [.git, node_modules/**/node_modules], verbose: true, execMap: { js: node, ts: ts-node }, watch: [src], env: { NODE_ENV: development }, ext: js,json }注意execMap里的 ts 映射到 ts-node 这一步不一定会成功因为很多项目根本没装 ts-node。所以这份初始化生成的配置更多是“一个起点”不是“标准答案”。拿到之后你需要按自己项目的实际情况调整。restartable: rs是很多人忽略的神器nodemon 运行期间你在终端里输入rs并回车可以手动触发一次重启这在改了环境变量但不想改文件、或者想强制重置进程状态的时候非常方便比 CtrlC 再重新启动干净得多。3. nodemon.json 核心配置项逐项拆解附一份可以直接抄的配置模板3.1 watch、ext、ignore告诉 nodemon 该看哪里最核心的三件套是 watch、ext、ignore。watch是一个数组指定要监听的目录或文件。默认情况下 nodemon 监听启动时的当前目录但实际项目中我强烈建议明确写出要监听的范围。比如一个典型的多端项目前端代码在client/后端代码在server/共享类型在shared/可以写watch: [server, shared]好处有两个一是避免监听无关目录造成无谓的系统开销二是避免误触发。比如你编辑client/里的组件时后端服务没理由要重启。注意一个细节watch里尽量用相对路径不要写绝对路径。比如/Users/me/project/server因为换台电脑、换个开发目录就失效了配置文件是跟着项目走的必须可移植。ext是扩展名列表默认是js,mjs,cjs,json。如果你的项目里有.ts文件或者你希望改.env、.yaml也触发重启就把它追加进去ext: js,mjs,cjs,json,ts,env这里有个常见误解以为设置了ext就只监听这些文件其实它只是“在这些扩展名里过滤触发条件”实际上没有写进 ext 的文件改了就不会触发。所以如果你发现改了.ts文件没反应第一反应应该看 ext 里有没有ts。ignore是忽略列表默认 nodemon 已经自动忽略.git和node_modules。真正需要你手动加的是“运行后产生的输出目录”。最经典的循环重启场景就是项目启动后写日志到logs/而它恰好落在监听范围里日志一变就重启重启又写日志无限循环。所以一定要把这类目录放进去ignore: [ dist/**, build/**, logs/**, coverage/**, *.log ]**表示递归任意层级这个 glob 语法很多人弄不清记一下就行。忽略规则的覆盖范围大于 watch优先匹配。3.2 delay、exec、env控制重启行为delay的存在是为了应对“高频保存”的场景。现在的编辑器大多有自动保存你连续修改文件时可能几秒内触发多次 change 事件。如果没有延迟nodemon 会连续重启好几次还时不时的“启动到一半又被新事件打断”报错和日志混在一起非常闹心。配置成delay: 1000表示收到文件变化事件后等 1 秒再重启这期间如果又有新变化会重置计时。单位是毫秒写成字符串或数字都可以。我个人习惯设 500 到 1000 毫秒既不影响实时性又能合并连击操作。exec指定要执行的命令。默认是node但你可以换成任何东西。比如配合ts-nodeexec: node --inspect9229 app.js注意参数顺序Node 的选项比如--inspect、--loader必须放在脚本文件之前否则会被当成传给脚本的参数。这一点很多人栽过后面单开章节细说。env是给被启动的进程注入环境变量env: { NODE_ENV: development, PORT: 3000 }这个的实用性在于你不需要在启动命令里写NODE_ENVdevelopment nodemon app.js配置文件能让所有参与项目的人都用同一套环境变量尤其适合团队开发时统一端口和调试开关。3.3 一份可以直接抄进项目的实例配置光说不练意义不大我把自己常用的配置贴出来场景是Express 后端 TypeScript 源码 编译产物在 dist 目录。{ watch: [server, shared], ext: ts,json, ignore: [dist/**, logs/**, coverage/**], exec: ts-node server/index.ts, delay: 500, env: { NODE_ENV: development }, verbose: true, restartable: rs }逐项解读watch限定范围只监听server和shared避免前端目录改动导致后端重启。ext只关心ts和json改别的文件不触发。ignore把编译产物、日志、覆盖率目录全部排除。exec直接用 ts-node 跑入口文件。delay: 500兜底高频保存。verbose: true启动时打印详细监听路径调试配置时非常有用。restartable: rs保持手动重启能力。如果你的项目里没有 ts-node只想跑纯 JavaScript配置会更简单{ watch: [src], ext: js,json, ignore: [node_modules/**], exec: node src/index.js }核心思路永远是明确监听范围、明确忽略范围、明确执行命令。3.4 配置到底放 nodemon.json 还是 package.json这是一个经常混淆的点。nodemon 支持两种项目级配置位置第一种项目根目录的nodemon.json。第二种package.json里的nodemonConfig字段比如{ name: my-project, scripts: { dev: nodemon src/index.js }, nodemonConfig: { watch: [src], ext: js,json, ignore: [dist/**] } }两者同时存在的优先级是nodemon.json 优先package.json 的 nodemonConfig 次之。命令行参数最高会覆盖配置文件里的同名字段。我个人的习惯是如果配置比较多、或者涉及exec这种较长命令单独建nodemon.json如果只是两三行小配置放package.json的nodemonConfig里就够了少一个文件更清爽。但无论选哪种都要提交到 Git别加到 .gitignore 里否则团队其他人拿不到这份配置。4. 改代码不重启、疯狂重启循环、进程杀不干净三个实战问题排查链路4.1 改了文件就是不重启日志没有任何反应这是被问得最多的问题。我的排查顺序是固定的你照这个链路走大概率十分钟内定位第一步确认 nodemon 真的在跑、真的在监听。如果你的配置里没有开 verbose先把它打开verbose: true重启 nodemon 后日志里会列出实际监听的路径和文件。如果日志显示它监听了server而你实际改的是client下的文件不触发就是理所应当的。第二步确认你改的文件扩展名在ext白名单里。改.ts文件但 ext 里只有js,json它是不会理你的。这个错误我在项目里亲眼见过好几个同事踩排查半天发现是扩展名没写进去。第三步确认它没有被 ignore 命中。有时候你配置了ignore: [src/**]然后奇怪为什么 src 下的改动不触发——因为你自己把整个 src 忽略了规则范围写太大。ignore 的匹配是贪婪的写了src/**就会忽略 src 底下一切文件。第四步如果以上都没问题那就要考虑系统层面的事件丢失了。Windows 下某些目录比如 OneDrive 同步目录、部分企业安全软件监控目录用默认的fs.watch会偶发收不到事件。这种情况最直接的解决办法是打开 legacyWatchlegacyWatch: true原理上默认监听使用的是文件系统事件回调响应快但依赖系统通知机制而 legacyWatch 会退回到轮询模式周期性扫描文件变化牺牲一点性能换来稳定。在 Docker 挂载卷、网络磁盘、部分虚拟机里这个开关几乎是必开的。第五步还没解决就考虑版本问题。老版本 nodemon1.x在 Node 20 上可能因为二进制模块或事件接口不兼容出现奇怪行为直接升级到 3.x 再试npm install --save-dev nodemonlatest4.2 无限重启循环“app crashed - waiting for file changes before starting”看到这句日志的时候很多人第一反应是“完了崩了”。其实它分两种情况。情况 A应用启动即崩溃。nodemon 发现子进程退出就打印app crashed - waiting for file changes before starting然后等待文件变化以便你改完能再拉起。这种时候真正的报错在更上面的 stderr 日志里诸如SyntaxError、Cannot find module、端口被占用等。先解决应用本身的报错循环自然消失。情况 B改动引起的重启循环。如果日志里不断出现restarting due to changes...然后进程起来又生成新文件新文件又触发变化无限循环。最常见的元凶就是日志文件或编译产物。排查手法是观察重启循环的“周期”。如果重启间隔接近你配置的delay那大概率是注册表里的什么文件在变。打开 verbose 看触发路径nodemon 会打印具体是哪个文件的变化导致重启。确定元凶之后把它加入 ignoreignore: [ logs/**, *.log, dist/**, .cache/** ]还有一个小技巧利用events配置来定位变化来源。nodemon 支持在 restart 事件时执行命令events: { restart: echo ---- restart triggered ---- }如果你在触发重启的瞬间这个 echo 前恰好有文件写入日志顺序会泄露线索。4.3 重启后端口被占用、EPERM、进程杀不干净在 Windows 上跑 Node 项目的朋友大概率都遇过这种情形nodemon 重启后新的子进程起不来报EADDRINUSE或者旧进程还在占着端口。原因出在信号机制上Linux/macOS 上 nodemon 默认通过 SIGUSR2 通知子进程重启子进程优雅退出后再拉起新的但 Windows 对这类“自定义信号”的支持不完整有时候进程树没有彻底清掉。解决方案是在配置里显式指定signal: SIGTERMSIGTERM 在 Windows 上有更好的兼容表现能让子进程更干净地退出。如果问题依然存在加一个更暴力的选项killOnExit: true这个配置会让 nodemon 在退出时强制终止整个子进程树而不是只杀根进程。代价是如果你有子进程再派生的孙进程可能不会被优雅清理但开发环境下通常无所谓稳定不占端口最重要。顺带一提如果你在终端里 CtrlC 退出 nodemon 后发现 node 进程还在后台跑可以先查端口lsof -i :3000拿到 PID 再kill -9强杀。开发环境下偶尔手动清理是正常的不用太纠结但配置好signal: SIGTERM之后这类情况会大幅减少。4.4 用 --verbose 和 rs 手动重启最快验证配置是否生效配置改完之后怎么快速知道有没有生效我的土办法是两步验证。第一步输入rs手动触发重启。如果配置从某个文件读取不了、或者解析报错nodemon 会直接输出错误信息而不会理你。这一步能初步确认配置文件格式没问题。第二步改一个无关紧要的文件比如往 README 里加个空格看是否触发重启。如果触发说明监听路径和 ext 都对如果不触发逐个检查 watch 覆盖、ext 白名单、ignore 黑名单。另外提醒一个小坑确保项目根目录只有一个nodemon.json没有在其他子目录里残留另一个副本。nodemon 会从当前工作目录向上查找配置文件如果你在server/子目录里执行nodemon它可能用的是另一个配置导致你以为“配置没生效”。我的经验是永远在项目根目录执行 nodemon或者用--config指明路径别依赖自动查找的玄学。5. 与 ts-node、ESM、VS Code 调试器组合时的关键配置细节5.1 TS 项目exec 里写 ts-node还是干脆换方案TypeScript 项目配 nodemon最直接的写法是exec: ts-node src/index.ts但这里有个隐藏问题新版 Node20默认启用 ESM 支持越来越激进而旧版 ts-node 对 ESM 场景的支持一直磕磕绊绊经常报ERR_UNKNOWN_FILE_EXTENSION之类错误。如果你的项目 package.json 里写了type: modulets-node 的默认配置很可能直接跑不起来。我的建议是分情况项目是 CommonJS 风格ts-node 配置正常那就保持现状稳定优先。项目走 ESM 或者你刚初始化新项目直接用tsx替代 ts-node更省心。用 tsx 时的配置exec: tsx src/index.tstsx 天然支持 ESM 和 TS 的最新语法几乎不需要额外配置文件兼容性比 ts-node 好太多。还有一个思路如果你项目已经用了ts-node-dev那其实可以不用 nodemon 了。ts-node-dev自带监听和进程重启原理上就是 ts-node 和文件监听的组合拳。不过它只适合 TS 项目而 nodemon 是语言无关的看你的技术栈统一性需求。我在实际项目里的偏好是纯 TS 后端用tsx nodemon不引 ts-node-dev因为团队成员可能前一个项目用的是 ts-node换 ts-node-dev 又要重新学一遍而 nodemon tsx 的组合对所有前端都足够熟悉。5.2 ESM 项目exec 里的参数顺序和 loader 配置ESM 场景下很多人把命令写错然后百思不得其解。关键是node的选项必须放在脚本文件之前。以用 ts-node 的 ESM loader 为例正确姿势是exec: node --loader ts-node/esm src/index.ts如果你写成exec: node src/index.ts --loader ts-node/esm那--loader会被当成传给src/index.ts脚本的参数Node 根本不会去加载它最后大概率报模块解析错误。同理要加--env-file、--import、--inspect这些 Node 运行时选项时统一放脚本文件之前。这个规则适合所有 Node 命令不只是 nodemon。config 里写错了nodemon 的日志不会特别明显经常表现为“进程能启动但某些功能不对”排查起来很迷惑。5.3 VS Code 调试launch.json 和 Attach 模式怎么配合用 nodemon 调试 Node 接口我最常用的方式是配置 launch.json让 VS Code 直接接管 nodemon 的子进程调试{ version: 0.2.0, configurations: [ { name: Debug with nodemon, type: node, request: launch, runtimeExecutable: nodemon, runtimeArgs: [--inspect9229, src/index.js], restart: true, console: integratedTerminal, skipFiles: [node_internals/**] } ] }关键点在于runtimeExecutable指定为nodemon这样 VS Code 启动的调试任务实际执行的是 nodemon而不是直接起 node。restart: true让 nodemon 每次重启后调试器能自动重新附着到新的子进程上。实测下来改完代码、nodemon 重启、调试器自动重连整个链路很顺滑。如果你不想用 launch 模式也可以先手动跑npx nodemon --inspect9229 src/index.js然后在 VS Code 里创建一个 attach 配置端口写 9229进程跑起来后按 F5 附加上去。attach 模式的好处是 nodemon 和调试器的生命周期解耦怎么重启都不影响调试会话。需要注意一个小坑--inspect端口别和其他项目冲突。如果同时开两个项目建议一个用 9229另一个用 9231避免调试器串线。5.4 在 Docker 和 CI 环境跑 nodemon轮询监听救大命Docker 里跑 nodemon 是另一类高频场景。本地开发文件系统的事件通知是正常的但 Docker Desktop 挂载卷尤其是 macOS 和 Windows 上的挂载卷里文件系统事件经常丢失现象就是“改代码后 nodemon 不重启”。解决方案有两个。第一个在 nodemon.json 里开 legacyWatchlegacyWatch: true第二个设置环境变量CHOKIDAR_USEPOLLINGtrue。因为 nodemon 的文件监听底层依赖的是 chokidar 库这个变量会强制 chokidar 进入轮询模式。在 docker-compose.yml 的开发服务里挂上environment: - CHOKIDAR_USEPOLLINGtrue轮询模式会让 CPU 占用有一些上升但开发容器里通常可接受稳定性优先。CI 环境的话我的建议很直接CI 里不需要跑 nodemon。CI 要做的是单次构建、单次测试不需要监听文件变化。nodemon 只装到 devDependencies生产镜像和 CI 流水线都不安装它既省安装时间也避免无意义的文件监控进程。最后想分享一个个人习惯很多人在 nodemon 配置上反复折腾往往不是配置项记不住而是没搞清楚 nodemon 的日志在说什么。第一次配置的时候就把verbose打开让日志直接告诉你它监听了什么、因为什么触发了重启、崩在哪个环节。这份日志比任何文档都管用。我现在的主力配置其实特别简单——一个二十行不到的nodemon.json加 package.json 里的两三条 scripts再没遇到过玄学问题。如果你现在正卡在某个“灵异事件”上按第 4 节的排查顺序走一遍大概率十分钟内能定位。工具这东西用熟了真的就是顺手的事。