1. 先把问题想清楚Mac 上为什么非得用 nvm 管 Node我手上这台 Mac 是 2020 年的 M1从做第一个前端项目开始到现在前后装过至少六七种不同版本的 Node。最开始我也是个官网下载 pkg 双击到底的人后来被项目逼着换版本、被团队成员吐槽你本地能跑我这跑不了才老老实实把 nvm 这套东西吃透。这篇就按我自己真实的操作顺序把 Mac 电脑安装 nvm 这件事从头到尾讲清楚——它是什么、能解决什么问题、装的时候会卡在哪、装完之后怎么用才不给自己挖坑。适合刚上手 Mac 做前端或者 Node 后端的朋友也适合那些已经装了 nvm 但只会nvm use一条命令、其余全靠猜的人。先给结论nvm 是 Node Version Manager 的缩写中文直接叫node 包版本管理工具也行但它管的不是包而是 Node 这个运行时本身。它的核心作用是让你在一台 Mac 上同时存在多个 Node 版本并且能按项目快速切换。这件事听起来简单但对日常开发的影响极大老项目锁死 Node 14新项目要 Node 20 甚至 22中间还夹着一个只能在 Node 16 上编译成功的原生模块——如果你只有一套全局 Node这三个项目你只能留一个跑得起来另外两个天天报错。nvm 干的事就是把 Node 从系统级唯一变成用户级多份、按需挂载。1.1 三种装 Node 的方式坑分别在哪在讲 nvm 之前先说清楚不装 nvm 的情况下Mac 上装 Node 一般有哪几条路以及它们各自会在什么时候咬你一口。把这三条路的缺点摆出来你就明白为什么我坚持推荐 nvm。第一条路官网下载 .pkg 安装包双击安装。这是最省事的方式一路继续点下去就完事。但它干了什么你可能不知道它会把 node 和 npm 二进制文件塞进/usr/local/bin/把 npm 的全局包目录设成/usr/local/lib/node_modules。问题出在权限上——/usr/local默认归 root 所有所以你每次npm install -g装全局命令大概率会遇到EACCES: permission denied。网上流传的解法是sudo npm install -g这招确实能装上但它会把全局目录里所有文件的属主改成 root之后普通用户再想更新、卸载就容易出各种奇怪的权限问题。另一个致命伤是这套东西只有一份你想换版本只能再下另一个 pkg装上去之后新旧文件互相覆盖which node指向哪里全看运气。第二条路Homebrew 装。brew install node或者brew install node18看起来优雅多了brew 帮你管理版本和路径。但 brew 的定位是系统包管理器它认为一个软件只需要一个版本。虽然现在支持node18、node20这种带版本号的 formula但你没法用一条命令快速切换node18和node20之间的切换靠的是brew link --overwrite --force node18每切一次就重写一遍软链接切完还要brew unlink另一个。切三四次你就烦了。而且 brew 升级 node 的时候会直接把你原来那个版本干掉全局包一起消失第二天上班发现pnpm命令没了。第三条路nvm。它不碰系统目录全部东西放在~/.nvm里每个 Node 版本一套独立的bin、lib、include全局包也跟着版本走互不干扰。切换版本只改一个软链接一秒钟的事。安装方式版本切换全局包隔离权限风险升级时的副作用官网 pkg需重装覆盖无高常见 EACCES覆盖旧版全局包丢失Homebrew需 link/unlink 两步部分隔离中偶尔需 sudo升级后旧版本被移除nvm一条命令秒切完全隔离低全程免 sudo老版本原样保留注意已经用 pkg 或 brew 装过 Node 的机器强烈建议先卸干净再装 nvm否则会出现nvm use了但node -v还是旧版本这种经典错乱原因是旧二进制还在/usr/local/bin里而那个目录在 PATH 里的优先级比 nvm 的目录高。1.2 nvm 能跑起来靠的到底是什么机制很多人装了 nvm 用得挺顺手但从没想过它凭什么能实现多版本共存。理解这一层后面排查问题会容易十倍。nvm 本质上是一堆shell 函数不是编译出来的二进制程序。你执行nvm use 20的时候它并没有启动什么后台进程而是在当前 shell 里改了两个东西一是把$NVM_DIR/versions/node/v20.x.x/bin这个路径插到PATH最前面二是直接操作一个叫~/.nvm/alias/default之类的符号链接。所以它天然有个限制——它只对当前这个终端会话生效而且必须用source的方式加载不能用./nvm.sh执行那样会在子 shell 里改环境变量父 shell 完全感知不到命令敲完看着成功实际啥都没变。再往下想一层既然 nvm 是 shell 函数那它就得知道自己该用哪个 shell 的语法。macOS 从 Catalina 开始默认 shell 从 bash 换成了 zsh所以网上大量老教程写的往.bash_profile里加配置对现在的 Mac 来说是无效的你必须往~/.zshrc里加。这也是新手装完 nvm 报command not found的头号原因后面会专门讲。还有一个容易被忽略的点nvm 和 npm 的全局包是绑定的。你给 Node 18 装了一个全局的typescript切到 Node 20 之后就找不到它了得重新装一份。这看起来是缺点其实是设计上的优点——它保证了每个 Node 版本下的全局环境是自洽的不会出现这个全局包是用 Node 18 编译的原生模块结果被 Node 20 加载这种诡异崩溃。2. 动手之前的环境盘点这步省不得我见过太多人上来就贴安装命令结果回车之后一堆红字然后开始在群里问为啥不行。问题八成不在安装命令本身而在他机器上已经存在一套旧的 Node 环境。花五分钟做个体检能省下后面半小时的排查。2.1 三条命令看清机器现状打开终端依次跑这三条把输出记下来或者截图存着which -a node node -v npm -v第一条which -a会列出所有能找到的 node 路径注意是-a不加这个参数只显示第一个。正常情况下干净的机器不会输出任何东西如果你看到/usr/local/bin/node或者/opt/homebrew/bin/node说明你之前装过 Node一会儿必须先卸掉。第二条和第三条是看当前版本。这里有个小细节值得留意如果node -v能输出但which -a node只显示一个路径那说明是 pkg 装的如果路径在/opt/homebrew/下面那是 brew 装的Apple Silicon 芯片的 brew 前缀是/opt/homebrewIntel 芯片是/usr/local这个区别在排查时会用到。顺便再补两条看看全局包和 PATH 长什么样npm ls -g --depth0 echo $PATH | tr : \n第一条会列出你当前装的全局包。装 nvm 之前把这些记下来很重要因为很多人切换版本之后发现yarn、pnpm、tsc全没了就是因为这些全局包没有迁移过去。第二条把 PATH 按行拆开看你能直观看到/usr/local/bin排在什么位置。2.2 旧 Node 怎么卸才干净如果是官网 pkg 装的官方没给卸载脚本只能手动删。执行下面这串命令注意每行前面有sudo会让你输密码sudo rm -rf /usr/local/bin/node sudo rm -rf /usr/local/bin/npm sudo rm -rf /usr/local/bin/npx sudo rm -rf /usr/local/lib/node_modules sudo rm -rf /usr/local/include/node sudo rm -rf /usr/local/share/man/man1/node.1删之前建议先确认一下这些路径存在可以用ls -la /usr/local/bin/node看一眼。node_modules那个目录千万别只删里面的包留个空目录npm 启动时会因为目录权限问题报错直接整个删掉最省心。如果是 brew 装的简单得多brew uninstall --ignore-dependencies node brew uninstall --force node18 brew cleanup--ignore-dependencies是因为有时候别的 formula 依赖 node不加这个参数 brew 会拒绝卸载。提示如果你不确定自己是怎么装的就两条路都走一遍。先跑 brew 的命令如果提示没装过那就是 pkg 装的再跑 sudo 那串。删完之后重新开一个终端窗口执行node -v如果提示command not found: node说明卸干净了可以进入下一步。2.3 要不要先装 Homebrew这是个取舍问题网上有两派做法一派说Mac 上必须先装 Homebrew万物皆可 brew另一派说nvm 就该用官方脚本装别绕弯子。我的建议要看你机器的实际情况。如果你已经在用 Homebrew 管其他软件那用brew install nvm是最省事的因为它顺手帮你处理了后续的升级。但 brew 装完之后不会自动帮你配 shell你必须手动建目录、手动往.zshrc加三行配置这步漏了就直接command not found很多人卡在这。另外 brew 的 nvm formula 是社区维护的版本更新会比官方仓库慢一点。如果你从没用过 Homebrew或者你所在网络环境下载 brew 本身就很费劲常见的报错是连不上 GitHub 的 raw 域名然后卡在Downloading那一步那直接走官方脚本更干净少一个依赖少一层故障点。我自己的机器上是两条都留着brew 管其他工具nvm 用官方脚本装。理由很简单——nvm 是唯一一个会侵入我 shell 启动流程的工具我宁愿它的安装方式完全透明可控出问题我知道去哪找。3. nvm 安装的完整实操过程这节是全文的核心我把每一步都拆开讲包括命令背后的动作。跟着走完你的 Mac 上就有 nvm 了。3.1 官方脚本安装一条命令背后做了四件事标准命令是这样的curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash这条命令拆开看是三段curl -o-把远程脚本内容输出到标准输出|管道传给bash执行。不要把它拆成先下载再执行两步时忘了检查内容直接从管道进来的脚本你是看不到的——这是行业惯例做法但心里要知道自己在执行什么。那个v0.40.1是版本号写死了比用master好因为 master 分支随时可能变写死版本能保证你和我装的是一套东西出问题好对照。想查最新版本号就去项目的 releases 页面看这个操作不需要任何特殊网络手段。脚本跑起来之后它实际做了这几件事尝试把 nvm 仓库克隆到~/.nvm目录。如果这个目录已经存在它会走git pull更新而不是重新克隆。检测你当前的 shell 类型找到对应的 profile 文件。往 profile 文件zsh 就是~/.zshrc末尾追加一段配置块。打印一段提示让你重新加载 shell 或者新开窗口。追加进去的配置大概长这样你可以装完后tail -n 20 ~/.zshrc看一眼确认export NVM_DIR$HOME/.nvm [ -s $NVM_DIR/nvm.sh ] \. $NVM_DIR/nvm.sh [ -s $NVM_DIR/bash_completion ] \. $NVM_DIR/bash_completion第一行定义目录变量第二行的-s判断文件存在且非空才source第三行是可选的命令补全bash 用户用zsh 用户其实可以不加载加载了会拖慢一点启动速度后面会讲怎么优化。3.2 网络不顺时的替代方案与加速配置如果上面那条 curl 命令跑了很久最后报Failed to connect或者直接超时说明脚本拉不下来。这时候有两个选择。选择一改用 Homebrew 装。先确认 brew 能用brew --version版本号能正常输出来就执行brew install nvm装完之后 brew 会提示你手动配置。按它的提示建目录mkdir ~/.nvm然后在~/.zshrc里加这几行Apple Silicon 机器的路径export NVM_DIR$HOME/.nvm [ -s /opt/homebrew/opt/nvm/nvm.sh ] \. /opt/homebrew/opt/nvm/nvm.sh [ -s /opt/homebrew/opt/nvm/etc/bash_completion.d/nvm ] \. /opt/homebrew/opt/nvm/etc/bash_completion.d/nvmIntel 芯片的机器把/opt/homebrew换成/usr/local即可。选择二仍然用官方脚本但配置 Node 下载镜像。这一步其实不管安装顺不顺都建议做因为 nvm 装完 node 之后要下载 Node 二进制包那个包有七八十兆从默认源拉经常龟速甚至断流。做法是在.zshrc里加一行环境变量export NVM_NODEJS_ORG_MIRRORhttps://npmmirror.com/mirrors/node这个变量名是 nvm 官方支持的加了之后nvm install就会去这个镜像拉包速度会明显不一样。同时建议把 npm 的 registry 也换掉不然你装全局包的时候还是慢npm config set registry https://registry.npmmirror.com这条命令会写进~/.npmrc可以随时用npm config get registry查看当前值想改回来执行npm config set registry https://registry.npmjs.org就行。注意镜像配置写在.zshrc里只对交互式终端生效。如果你后面要写脚本或者配置自动化流程记得在那个环境里也把这个变量带上否则会出现我本地装得飞快自动化流程里卡到超时的差异。3.3 让配置生效并验证配置写完必须做的一件事重新加载 shell。有两个办法选一个source ~/.zshrc或者干脆关掉终端窗口重新开一个。我个人推荐第二种因为它能顺便验证新窗口能不能拿到正确的环境——这才是真实使用场景你不可能每次写完代码都 source 一遍。然后验证command -v nvm这里有个坑必须说清楚验证 nvm 是否可用要用command -v nvm不要用which nvm。原因回到前面讲过的机制——nvm 是 shell 函数不是可执行文件which命令只会在 PATH 里找可执行文件所以它永远找不到 nvm会给你一个假警报。这个细节 nvm 官方文档专门强调过但网上大量教程写错了导致很多人明明装好了却以为失败。command -v nvm应该输出nvm这个词本身。再跑一条看版本nvm -v正常会输出类似0.40.1的版本号。到这一步安装就算完成了而且还是裸装状态一个 Node 都没有。别急下一节讲怎么装 Node。4. nvm 的高频命令与关键配置细节装好只是开始真正决定你用得舒不舒服的是配置细节。这一节我挑几个最容易踩坑的地方讲都是我自己和身边同事反复遇到过的。4.1 日常必背的命令清单先把常用命令列一遍你不需要背用到的时候翻一下就行nvm install 20 # 装 Node 20 的最新版 nvm install 20.11.1 # 装指定精确版本 nvm install --lts # 装当前 LTS 线的最新版 nvm use 20 # 切换到 Node 20当前会话生效 nvm use --delete-prefix 20 # 切换并清掉冲突的旧前缀 nvm alias default 20 # 把 20 设为默认版本新窗口默认用它 nvm ls # 列出本机已安装的所有版本 nvm ls-remote --lts # 列出远端所有 LTS 版本 nvm current # 显示当前正在使用的版本 nvm uninstall 18 # 卸载 Node 18 nvm which 20 # 查看 20 对应的二进制路径这里面nvm install有个细节值得说不加精确版本号时它会去拉该大版本线下的最新小版本。这个行为在 90% 的场景下是你想要的但在需要严格复现问题的场景下反而不利——你今天nvm install 20装的是 20.11.1下个月同事执行同一条命令装的是 20.13.0然后你俩跑出不同结果。所以凡是打算提交到仓库里给团队共用的配置一律写精确版本号。4.2 alias default不设它必踩的坑这是新手最容易忽略、也最影响体验的一件事。现象是这样的你nvm use 20之后写的代码跑得好好的关掉终端第二天开新窗口执行node -v发现又变回system或者某个老版本了。原因是nvm use的作用范围就是当前这个终端会话。新开的窗口会回到默认版本而默认版本如果没设过nvm 会尝试用系统里那个 Node如果系统里没有就会报command not found。解法就一条命令nvm alias default 20这条命令会在~/.nvm/alias/default里写一条记录之后每个新终端都会自动用 20。验证方式关掉所有终端窗口开一个新的执行nvm current应该直接输出v20.x.x。我个人的习惯是再加一条无版本的 alias 兜底因为有些老脚本会读nvm use defaultnvm alias default 20.11.1 nvm alias lts/* 20.11.1提示如果你之前用 pkg 装过 Node设 default 之后可能会遇到nvm use报 Your user directory contains a .npmrc file with a prefix option 或者切换后版本没变的情况这时候用nvm use --delete-prefix 20它会顺手清理掉冲突的 prefix 配置。4.3 全局包迁移别一个个重装每装一个新 Node 版本全局包都是空的。如果你全局装了pnpm、tsc、eslint这些常用工具一个个重装很烦。nvm 给了个批量迁移参数nvm install 20 --reinstall-packages-from18意思是装 Node 20 的时候把 Node 18 里那些全局包全部在新版本下重装一遍。这个参数还有两个变体--reinstall-packages-fromdefault从默认版本迁--reinstall-packages-fromnode从系统 Node 迁。这个功能有个前提得知道它读的是旧版本里的全局包列表然后在新版本下执行npm install -g所以新版本必须有网络能访问 registry。如果你配了镜像它走的是镜像速度没问题。另外原生模块比如node-gyp相关的会重新编译一次编译需要 Xcode Command Line Tools如果你的机器上没装会报gyp: No Xcode or CLT version detected。装一下就行xcode-select --install4.4 全局目录隔离带来的两个连带影响前面说过 nvm 让每个 Node 版本的全局包完全隔离这个设计有好处但也有两个连带影响不知道的话会觉得莫名其妙。影响一全局命令在切换版本后突然消失。你在 Node 18 下装了全局pnpm切到 20 之后敲pnpm -v报 command not found。这不是 bug是预期行为。解法就是上面那个--reinstall-packages-from或者在新版本下重装一遍。影响二npm root -g的路径会随版本变。有时代码里硬编码了全局模块路径切版本后就失效。判断当前全局目录用npm root -g输出会类似/Users/你的用户名/.nvm/versions/node/v20.11.1/lib/node_modules。看到.nvm/versions/node/这串就说明当前环境是 nvm 管的是正常状态。4.5 .nvmrc让项目自己声明要什么版本如果团队里每个人都有 nvm那最省事的协作方式就是在项目根目录放一个.nvmrc文件里面就写一行版本号20.11.1别人 clone 下来之后在项目目录执行nvm usenvm 会自动读这个文件并切到对应版本连版本号都不用敲。再进一步可以做成进入目录自动切换。在~/.zshrc末尾加这段这是 nvm 官方文档给的 zsh 集成方案autoload -U add-zsh-hook load-nvmrc() { local node_version$(nvm version) local nvmrc_path$(nvm_find_nvmrc) if [ -n $nvmrc_path ]; then local nvmrc_node_version$(nvm version $(cat ${nvmrc_path})) if [ $nvmrc_node_version N/A ]; then nvm install elif [ $nvmrc_node_version ! $node_version ]; then nvm use fi elif [ $node_version ! $(nvm version default) ]; then nvm use default fi } add-zsh-hook chpwd load-nvmrc load-nvmrc这段逻辑做的事情是每次你cd换个目录它就往上找有没有.nvmrc。找到的话如果本地没装这个版本就自动装装了但版本不对就自动切没找到.nvmrc就切回默认版本。这里有个代价必须提醒这段代码会明显拖慢终端启动和新窗口打开的速度因为每次chpwd都要执行一遍nvm version而 nvm 的加载本身就不便宜。如果你机器上开了多标签终端可能会感觉到微妙的卡顿。我的取舍是只在需要维护多个老项目的机器上开这个功能日常只做一两个项目的话手动nvm use完全够用。5. 多项目并行时的版本管理组合拳实际工作里nvm 从来不是单独使用的它总是和 npm、pnpm、corepack 这些东西配合。这一节讲怎么把它们串起来用避免职责打架。5.1 nvm、corepack、pnpm 三者各管什么我见过的最混乱的配置是这样nvm 管 Node 版本corepack 也在管包管理器版本然后用户又手动npm i -g pnpm装了一份全局 pnpm。三个东西同时想决定用哪个 pnpm结果就是今天能跑明天崩。正确的分工应该是nvm管 Node 运行时版本这是最底层。corepack管包管理器pnpm、yarn的版本它跟着 Node 走Node 20 自带了 corepack。pnpm / yarn自己的全局安装尽量不要做交给 corepack。具体做法是先启用 corepackcorepack enable corepack prepare pnpm9.1.0 --activate或者在项目package.json里加一段{ packageManager: pnpm9.1.0 }加了这段之后corepack 会在你执行pnpm命令时自动下载并使用这个精确版本跟 Node 版本解耦但又能配合。如果你就是想全局装一份 pnpm比如为了在任意目录用pnpm create那么用 nvm 的全局安装是安全的npm install -g pnpm它会被装进当前 Node 版本的全局目录切版本后需要重装。这个行为虽然麻烦但至少不会出现跨版本污染。5.2 老项目和新项目并行的切换节奏我手上常驻三个版本Node 14 给一个 2019 年的老后台Node 18 给一个维护中的中台项目Node 20 给所有新项目。日常切换节奏大概是这样的早上起来先看一眼今天要碰哪个项目cd过去之后cat .nvmrc看看声明的是什么版本然后nvm use。切换之后第一件事是确认全局工具还在——尤其是pnpm和tsc因为老项目往往依赖一个指定版本的 TypeScript CLI。如果不在就在这个 Node 版本下补装一次装完之后它就长期待在这个版本里了下次切过来还在。有个小技巧能减少重复劳动把三个版本都预先装好、都把常用全局包装好之后切换就是纯切 PATH秒切不用等下载。代价是磁盘占用一个 Node 版本加上全局包大概 200 到 500 兆三个版本一个多 G对现在的 Mac 硬盘来说不算什么。5.3 团队协作里该约定什么如果团队里人人都用 nvm有几件事值得写进 README 或者项目规范第一.nvmrc必须提交到仓库并且写精确版本号。不要写20要写20.11.1理由前面讲过。第二package.json里的engines字段也要写这是给不用 nvm 的人或者 CI 环境看的{ engines: { node: 20.11.1 21, pnpm: 9 } }加上engine-strict之后版本不匹配会直接报错而不是警告npm config set engine-strict true第三包管理器只用一种。同一个仓库里既有package-lock.json又有pnpm-lock.yaml是最典型的团队协作灾难来源因为不同人执行npm install和pnpm install出来的依赖树可能不一样然后就出现我本地能跑的经典场景。6. 报错排查实录这些问题我都遇到过下面这些报错我几乎每一个都亲身踩过或者帮同事排查过。整理成速查方式方便你按症状对号入座。报错现象根本原因解决方法nvm: command not found配置写进了.bash_profile而当前是 zsh把配置移到~/.zshrc并source新窗口里 nvm 又没了.zshrc写错路径或用了错误的分支tail -n 20 ~/.zshrc检查路径是否存在nvm use后node -v没变PATH 里旧 Node 优先级更高nvm use --delete-prefix ver或删掉旧 Nodenvm install卡在下载不动默认源速度受限配NVM_NODEJS_ORG_MIRROR镜像变量nvm install报 gyp 错误缺 Xcode Command Line Toolsxcode-select --install全局包切换版本后消失全局目录按 Node 版本隔离--reinstall-packages-from迁移npm install -g报 EACCES残留了系统级 npm 全局目录卸载旧 Node确认npm root -g在~/.nvm下终端启动明显变慢.nvmrc自动切换钩子 bash_completion注释掉不必要的加载或关掉 chpwd 钩子nvm ls-remote超时远端版本列表接口访问不畅配镜像变量或直接nvm install 具体版本号6.1command not found这一类的排查顺序这个报错占了所有 nvm 问题的六成以上我一般按这个顺序查第一步确认配置写对文件了。执行echo $SHELL输出/bin/zsh就是 zsh配置必须写~/.zshrc输出/bin/bash才是 bash写~/.bash_profile。macOS 十点十五以后的系统默认全是 zsh但如果你手动改过或者用了某些终端工具改了默认 shell情况就不一样。第二步确认配置内容本身没问题。跑tail -n 20 ~/.zshrc看有没有NVM_DIR那三行。如果用了 brew 安装检查路径[ -s /opt/homebrew/opt/nvm/nvm.sh ]里的文件是不是真的存在用ls /opt/homebrew/opt/nvm/nvm.sh验证。第三步确认加载没被静默失败打断。.zshrc里如果前面有某行配置抛错了后面的可能不执行。可以在终端里手动执行一遍那两行 source 命令看看有没有报错输出。手动 source 之后command -v nvm有输出就说明配置内容是对的问题在加载时机。6.2 下载慢和下载失败的处理nvm 装 Node 的时候是从官方分发站点拉压缩包几十兆到上百兆。在国内网络环境下最常见的问题是速度慢到几 KB 每秒或者中途断流报curl: (56) Recv failure。处理方式就是前面提过的镜像变量。要注意变量名一个字母都不能错NVM_NODEJS_ORG_MIRROR全大写下划线分隔。写进.zshrc之后要source一次或者新开窗口。验证是否生效有个土办法echo $NVM_NODEJS_ORG_MIRROR有输出就说明变量在环境里了。还有一种情况是nvm ls-remote特别慢或者直接失败那是因为它要去拉一个很长的版本列表文件。这种情况下你其实不需要 ls-remote直接nvm install 20.11.1这种指定精确版本的方式跳过了列表查询会快很多。6.3 权限类报错的根治方式EACCES: permission denied, access /usr/local/lib/node_modules这个报错本质上是 npm 想往系统目录写东西但你没权限。网上流传最广的解法是sudo npm install -g我强烈建议不要用这个办法。正确做法是确认 npm 的全局目录已经被 nvm 接管npm root -g输出应该是/Users/你的用户名/.nvm/versions/node/vX.Y.Z/lib/node_modules。如果输出的是/usr/local/lib/node_modules或者/opt/homebrew/lib/node_modules说明环境变量被别的配置覆盖了。检查一下.zshrc里有没有单独设过NPM_CONFIG_PREFIX或者PREFIX有的话删掉因为 nvm 环境下不应该有这些。另外如果你之前用 sudo 装过全局包那些文件属主会变成 root即使切到 nvm 之后也可能因为残留的.npmrc里的 prefix 配置而继续报错。检查cat ~/.npmrc把prefix开头的那行删掉。6.4 一个容易忽略的排查工具nvm 自己带了 debug 模式nvm debug它会输出一堆环境信息包括当前 shell、nvm 版本、NVM_DIR 路径、PATH 内容、有没有冲突的 Node 安装等等。当你实在看不出问题在哪把这个输出贴出来基本上一眼就能定位。我自己排查同事的问题时都是先让他跑这一条。7. 长期维护升级、清理和磁盘占用装了 nvm 之后它就成了你 Mac 上长期存在的一部分需要用久了也不出问题的方式维护。这一节讲三个实际会遇到的事情。7.1 nvm 自身怎么升级nvm 是脚本升级方式取决于你怎么装的官方脚本装的重新跑一遍安装命令就行它会检测到~/.nvm已存在然后走 git pull 更新curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bashbrew 装的brew update brew upgrade nvm升级完之后.zshrc里的配置一般不用改除非新版本改了文件的相对位置。什么时候需要升级 nvm我的判断标准是当你想装的某个新 Node 版本在nvm ls-remote里找不到的时候大概率是 nvm 太老不认识新的版本命名规则。这种情况直接升级。7.2 旧版本和缓存的清理用久了机器上会堆一堆不用的 Node 版本。先看看有哪些nvm ls输出里带-的是当前版本default标记的是默认版本。其余的都是可以清理的对象。卸载某个版本nvm uninstall 16.20.0如果提示Cannot uninstall currently-active node version说明它正在被使用先nvm use切走再卸。另外 nvm 下载的 Node 压缩包默认不会长期保留用完会清但.nvm/.cache里可能还有残留。看一眼占多少du -sh ~/.nvm du -sh ~/.nvm/.cache~/.nvm整个目录如果超过 5 个 G那就该清一清了。.cache目录可以放心删rm -rf ~/.nvm/.cache下次装版本时会重新下。顺便说一个很多人关心的点Mac 的系统数据占用里开发相关的文件其实很显眼。~/Library/Caches下的各种缓存、Xcode 的 DerivedData、还有各个项目里的node_modules加起来几十个 G 很常见。nvm 本身占的不算多但每个 Node 版本下的全局包会累积。清理思路是先nvm ls看有几个版本把只用过一两次的卸掉然后针对当前在用的项目执行一次find . -name node_modules -maxdepth 3 -type d -prune -exec du -sh {} 看看哪个项目的依赖特别大清掉不活跃项目的node_modules需要的时候重新pnpm install就行反正有 lock 文件保证结果一致。7.3 一个我用了很久的收尾习惯每隔一两个月我会花五分钟做一次环境对账步骤很简单开一个新终端依次执行nvm --version、nvm current、npm root -g、node -v、pnpm -v把这五条的输出截个图存起来。下次环境出问题时跟这张图对比一下差异出在哪一目了然。这个习惯帮我省过很多时间。有一次我发现自己明明设了nvm alias default 20新窗口却还是 Node 18对比截图之后才发现是.zshrc里某次改配置的时候把 nvm 的加载顺序挪到了nvm alias default生效之前的位置导致 alias 读的还是上一次的缓存。这种问题不看基线根本发现不了。我个人在实际操作中的体会是Mac 上装 nvm 这件事本身十分钟就能搞定真正花时间的是让它和你的项目结构、团队约定、终端习惯磨合到位。最省心的路径是官方脚本装、配好镜像变量、设一个 default 别名、项目里放.nvmrc、全局包尽量别装改用 corepack 管。这套组合跑一两年基本不用动遇到问题按报错速查表对号入座就行。