说个很多同行都遇到过的场景接手一个旧项目npm install直接报一堆gyp编译错误查半天发现项目锁在 Node 16而你机器上装的是 Node 22换个新项目又要求 Node 20 以上于是你开始卸载重装、改 PATH、清理残留一个下午就这么没了。nvmNode Version Manager就是专门解决这件事的工具——它让同一台机器上并存多个 Node 版本用一条命令切换互不干扰。这篇内容我把 Windows 和 macOS 两条路线完整走一遍从下载、安装、shell 配置到日常使用和故障排查全部摊开讲。适合三类人看刚接触 Node 生态、机器上只装过一个版本的新手需要同时维护多个老项目的中高级开发以及负责给团队统一环境的角色。装过但总觉得有点玄学的同学也建议往下看很多坑其实都有明确原因。1. 为什么值得花十分钟把 nvm 装明白1.1 多版本共存这件事绕不过去Node 的版本迭代速度摆在那儿每年两个大版本LTS 长期维护版也有明确的起止时间。企业项目为了省心通常锁在一个 LTS 上不动个人折腾新特性又会装最新版。这两类需求在一台电脑上同时存在靠手动卸载重装是撑不住的——不同版本的 npm 全局包互相覆盖、原生模块比如一些需要编译的 C 扩展缓存错乱、node_modules里锁死的二进制文件对不上 ABI都是真实会炸的点。nvm 的思路很朴素每个 Node 版本单独一个目录切换时只改当前 shell 的PATH让系统优先找到目标版本的bin目录。所有版本的文件都在只是当前生效的那个变了。理解这句话后面很多现象你就能自己推出来——比如为什么切换是瞬间完成的、为什么全局包装在某个版本下换个版本就看不见了、为什么开新终端窗口版本又变了。1.2 nvm 其实是两个不同的东西别装错这是最容易踩的第一个坑。你在 macOS 或 Linux 上搜到的 nvm和 Windows 上用的 nvm不是一个代码库。对比项POSIX 版 nvmWindows 版 nvm-windows实现语言Shell 脚本Go 编译的可执行文件运行方式必须被source加载进当前 shell独立 exe直接命令行调用版本切换机制改环境变量PATH改一个软链接指向常用命令nvm ls、nvm install、nvm usenvm list、nvm install、nvm use.nvmrc支持原生支持可做自动切换较新版本支持读取但行为不完全一致卸载方式删目录 删配置行走卸载程序还得清理软链接和 PATH命令看着像底层差得远。所以你在网上抄到一段curl一行安装的命令拿到 Windows 的 PowerShell 里执行是没用的反过来把nvm-setup.exe的教程套到 macOS 上也不行。先把这条分清能省掉后面一半的困惑。1.3 动手前先做三件小事第一件清点现有的 Node。macOS 上执行which -a nodeWindows 上在 PowerShell 里执行Get-Command node -All。如果输出指向/usr/local/bin/node或者C:\Program Files\nodejs\node.exe说明你之前用官方安装包装过这个版本会和 nvm 抢 PATH。建议先卸干净官方安装包装的版本有专门的卸载脚本或卸载入口别直接删目录了事。第二件确认 shell 类型。macOS 从 Catalina 起默认是 zsh配置文件是~/.zshrc如果你手动改过用 bash那就是~/.bash_profile或~/.bashrc。确认命令是echo $SHELL。写错文件是装完 nvm 命令找不到的头号原因。第三件给安装目录选个干净的路径。~/.nvm这个默认位置就很好。特别提醒一句不要把 nvm 目录放在任何同步盘的同步范围内那些工具会在后台不停扫描文件、加锁node_modules动辄几万个文件同步冲突和权限错误会没完没了。2. macOS 上的安装脚本、配置、验证一条龙2.1 三种装法怎么选我为什么推荐脚本macOS 上主流有三条路官方提供的一键安装脚本、包管理器安装、手动克隆仓库。三者都能用但适用场景不一样。官方脚本是作者维护的主要分发方式它会自动把仓库克隆到~/.nvm往你的 shell 配置文件里追加加载语句顺手装好命令补全。优点是版本和目录结构都是标准答案后面查文档、看报错都对得上。包管理器装的版本装完不会自动写配置还得自己补而且它管的 nvm 和它管的 node 之间偶尔会打架node命令的优先级容易乱。手动克隆最灵活适合你想锁死某个提交、或者机器上完全没法联网跑脚本的情况代价是补全和配置都得手工来。我一般推荐脚本安装除非你在受限网络环境里连脚本都拉不下来。下面重点讲这条。2.2 一键脚本安装的完整过程先把脚本内容下载下来看一眼再执行这是好习惯——不是多疑是这类curl | bash的东西你至少要知道它动了哪些文件。# 第一步只下载不执行先看内容 curl -o- 官方仓库中对应版本的 install.sh 地址 /tmp/nvm-install.sh less /tmp/nvm-install.sh # 第二步确认无误后执行本地这份 bash /tmp/nvm-install.sh地址从官方仓库的发布页面上找选最新的版本标签拼成install.sh的完整路径。脚本干的事大概分四步git clone仓库到$NVM_DIR默认~/.nvm、检测当前 shell 类型、往对应的 profile 文件里追加加载块、尝试加载补全脚本。整个流程几十秒取决于网络。脚本支持几个有用的参数值得记住# 装完不自动 use 任何版本防止刚装完就有个默认版本顶在前面 bash install.sh --no-use # 指定安装目录 NVM_DIR$HOME/tools/nvm bash install.sh # 指定写入哪个配置文件 PROFILE$HOME/.zshrc bash install.sh # 完全不写配置文件只克隆代码 PROFILE/dev/null bash install.sh--no-use这个参数我强烈建议加上。不加的话脚本在加载时会尝试用一个默认版本有时候会把系统里原有的 node 顶掉排查起来更乱。装的时候干净一点后面主动权在你手上。2.3 shell 配置文件里到底要写什么脚本自动写好的是这么几行你自己手动补也得是这几行export NVM_DIR$HOME/.nvm [ -s $NVM_DIR/nvm.sh ] \. $NVM_DIR/nvm.sh [ -s $NVM_DIR/bash_completion ] \. $NVM_DIR/bash_completion这三行的门道不少。-s判断的是文件存在且大小不为零比-f更严格一点防止一次坏的下载留下空文件把整个 shell 搞挂。\.是source的 POSIX 写法两者等价\.兼容性更好。保证前一个条件不成立就跳过不会报错刷屏。最关键的一点nvm.sh 必须被 source不能当脚本执行。原因在于 nvm 的全部工作都是修改当前 shell 的环境变量——PATH、NVM_BIN、NVM_INC这些。执行一个脚本会开一个子进程子进程里改再多变量退出后对父 shell 没有任何影响你会看到命令执行成功了但nvm就是不认识。这个原理想通了以后遇到我明明跑了但还是 command not found第一反应就该是我是不是用./nvm.sh跑的。另外注意这段配置要放在 profile 文件的靠后位置。如果你文件里更早的地方有一行把PATH整个重写了比如某些老教程里写的export PATH/usr/local/bin:$PATHnvm 改过的 PATH 会被冲掉。2.4 验证与第一次版本切换新开一个终端窗口或者source ~/.zshrc依次验证command -v nvm # 应该输出 nvm注意这里必须用 command -v不能用 which nvm --version # 输出版本号 nvm ls # 列出已安装的版本刚装完是空的 nvm ls-remote --lts # 列出所有仍在维护的 LTS 版本这里有个新手常掉的坑which nvm什么都不输出。原因是 nvm 是一个 shell 函数不是磁盘上的可执行文件which只找可执行文件。判断 nvm 有没有加载成功用command -v nvm或者直接type nvm。装第一个版本nvm install 20 # 装 20 系列的最新版 nvm install --lts # 装最新的 LTS nvm alias default 20 # 把 20 设为新终端默认版本 nvm use 20 node -v # 确认生效 npm -vnvm install 20里的20是个模糊匹配nvm 会解析成该大版本下最新的小版本。装的时候你能看到它在下载一个.tar.xz包然后解压——这说明 nvm 用的是官方预编译好的二进制不是本地编译所以速度取决于网络整个过程也就十几秒。有个细节值得注意nvm 给每个版本配的 npm 是那个 Node 版本发布时自带的版本。Node 20 刚发布时的 npm 可能是 10.0现在 Node 20 已经更新到 20.1x 了但如果你装的是早期的具体小版本npm 也会是老的。想用最新 npmnvm install 20 --latest-npm这条会把该版本下的 npm 升到当前最新。但我要提醒一句npm 大版本升级偶尔会有行为变化团队项目里别乱升跟着 lockfile 走更稳。3. Windows 上的安装nvm-windows 的取舍与细节3.1 为什么 Windows 用的是另一个项目前面说过POSIX 版 nvm 是一堆 shell 脚本靠source修改当前进程环境变量这套机制在 Windows 的 cmd 和 PowerShell 里根本没有对应物。所以 Windows 上需要一个原生程序来做同样的事nvm-windows 就是这么来的。它换了个实现思路不折腾 PATH 的顺序而是在 nvm 的安装目录里放各个 Node 版本然后维护一个软链接symlink让C:\Program Files\nodejs这个固定路径始终指向当前选中的版本。系统 PATH 里永远只有这一个路径切换版本就是改软链接的指向。这个设计的好处是 PATH 永远干净坏处是所有操作都跟文件系统权限挂钩——这也是 Windows 上大部分报错的根源。3.2 安装向导里的两个路径选项下载nvm-setup.exe后一路下一步但中间有两个路径选择必须停下来看一眼。第一个是nvm 的安装根目录对应NVM_HOME默认在用户目录下的AppData\Roaming\nvm。这里会存 nvm 本体和所有下载的 Node 版本占空间可能好几个 G如果你的系统盘吃紧可以换到别的盘。第二个是Node 软链接路径对应NVM_SYMLINK默认C:\Program Files\nodejs。这个路径就是系统 PATH 里出现的那一个。这两处有个共同禁忌不要带空格不要带中文。C:\Program Files\nodejs里的空格是官方默认值能跑但你在命令行里手敲路径、写脚本、配 CI 的时候就得处处加引号。我一般建议把软链接改到C:\nodejs这种干净路径一劳永逸。中文路径的问题更直接某些老工具链读路径会乱码。权限这块要提前想清楚。C:\Program Files是受保护目录创建和修改软链接需要管理员权限。也就是说在默认配置下你每次nvm use都得以管理员身份开终端。要么改路径避开受保护目录要么接受这个约束。还有一个高频翻车点装之前先把原有的 Node 卸干净。官方安装包装的 Node 会占着C:\Program Files\nodejs这个位置nvm 安装时要么报目录已存在要么装完了两个东西互相覆盖。正确顺序是控制面板卸载 Node → 手动确认C:\Program Files\nodejs目录已经删掉 → 检查系统环境变量里有没有残留的 Node 路径 → 再装 nvm。3.3 装完先别急着用settings 与镜像配置nvm-windows 的行为由安装目录下的settings.txt控制装完第一件事就是打开它看看。里面通常长这样root: C:\Users\你的用户名\AppData\Roaming\nvm path: C:\nodejs node_mirror: Node 二进制包的分发地址 npm_mirror: npm 包的分发地址root和path就是前面那两个路径。下面两行是下载源如果你所在网络拉官方源速度不理想可以换成任意一个你能正常访问的公共镜像地址。较新版本也支持用命令改不用手编辑文件nvm node_mirror 镜像地址 nvm npm_mirror 镜像地址改完记得nvm root确认一下当前生效的目录别改错文件了。如果你机器上装了多个 nvm 副本比如以前装过没卸干净这种错位会非常迷惑。配置完装第一个版本nvm install lts nvm use 20.11.1 node -v npm -v注意 Windows 版的nvm install lts装的是当前最新 LTS但nvm use的时候必须给完整版本号nvm use 20这种模糊写法在部分版本上不被接受。装完先nvm list看一眼完整版本号再 use能少一次报错。3.4 验证清单与首个 Node 版本落地装完跑一遍这几条全部通过再开始干活检查项命令预期结果nvm 是否可用nvm version输出类似 1.1.x版本列表是否正常nvm list列出已装版本当前版本带*远程版本能否拉到nvm list available输出一张版本表软链接是否建成nvm use 完整版本号输出Now using node vX.Y.ZNode 是否生效node -v输出对应版本npm 是否可用npm -v输出 npm 版本全局包路径是否正确npm config get prefix应指向 nvm 下的对应版本目录最后一条特别重要。如果npm config get prefix输出的不是 nvm 版本目录而是类似C:\Users\xxx\AppData\Roaming\npm这种东西说明你的.npmrc里有一行prefix配置在捣鬼。这个配置会让所有全局包装到同一个公共位置nvm 的版本隔离就形同虚设了——你在 Node 18 下装的全局 CLI 工具切到 Node 22 还在但它可能根本跑不起来。把这行删掉。4. 日常操作把版本切换变成肌肉记忆4.1 高频命令速查真正每天在用的就那么几条我把两个平台的差异标出来用途macOS / LinuxWindows装指定版本nvm install 18.20.4nvm install 18.20.4装最新 LTSnvm install --ltsnvm install lts切到某版本nvm use 18.20.4nvm use 18.20.4列出已装nvm lsnvm list列出可装nvm ls-remotenvm list available看当前版本nvm currentnvm list带*号设默认版本nvm alias default 18nvm alias default 18看版本真实路径nvm which 18nvm root卸载某版本nvm uninstall 18.20.4nvm uninstall 18.20.4临时关掉 nvmnvm deactivatenvm off有几个命令值得单独说一下用途。nvm exec 18 node -v用指定版本跑一条命令不切换当前 shell。写脚本的时候很有用比如你要在 Node 18 下跑一次构建跑完后面还是原来的版本。nvm alias除了default还能自定义名字nvm alias work 20.11.1之后nvm use work就行。团队里版本命名统一的话这套别名挺省事。nvm unload只影响当前 shell把 nvm 的函数和变量全清掉nvm命令立刻消失。做实验前用它比开新窗口快。4.2 用 .nvmrc 把项目版本钉死光靠个人记忆切换版本团队里迟早出事A 用 18 提交的代码B 用 22 拉下来跑某天某个依赖的行为变了排查两天。解决办法是在项目根目录放一个.nvmrc文件内容就一行版本号18.20.4写大版本号也行18nvm 会自动匹配该大版本下最新已装的版本。然后nvm use # 不带参数自动读 .nvmrc nvm install # 不带参数装 .nvmrc 指定的版本再进一步让它进目录就自动切换。在~/.zshrc里加一段chpwd钩子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这段逻辑是进目录时找.nvmrc找到了但版本没装就自动装装了但没在用就切过去没找到.nvmrc就退回默认版本。用 bash 的话把chpwd那套换成PROMPT_COMMAND里调用load-nvmrc。有个副作用得说清楚每次cd都可能触发一次版本切换如果切到的版本缺全局包你的命令行工具会短暂消失。习惯就好或者只在确实需要的项目里开这个功能。Windows 上的.nvmrc支持要看版本较新的 nvm-windows 能在目录里执行nvm use时读取它但兼容性和 POSIX 版不完全一样遇到不生效的情况就直接敲完整版本号别在工具上耗时间。4.3 全局包的迁移与重装这是 nvm 用户最常问的问题之一我换了版本pnpm怎么没了因为全局包是装在每个 Node 版本自己的目录里的路径大概是~/.nvm/versions/node/v20.11.1/lib/node_modules。切到另一个版本那里是空的自然没有。这不是 bug是设计——正是这种隔离保证了版本之间的干净。但重复装也烦。nvm 提供了迁移命令nvm install 20 --reinstall-packages-from18装 20 的同时把 18 下所有全局包装过去。已经装好了也能补# 先看看 18 下有什么 nvm exec 18 npm ls -g --depth0 # 再迁移 nvm reinstall-packages 18注意--depth0不加这个参数会把整棵依赖树都列出来几百行刷屏。另外要提醒的是迁移只搬包不搬配置。那些把状态存在全局包目录里的工具某些 CLI 的缓存、登录凭据迁过去可能还得重新登录一次。还有一种选择是干脆不装全局包。现在主流做法是把 CLI 工具放进项目的devDependencies用npx或npm exec调用。好处是版本跟项目走、CI 里天然一致、别人克隆下来不用额外配置。我自己现在全局只留两三个真的跨项目高频的东西其余全下沉到项目里了。5. 报错排查实录那些年踩过的坑5.1 macOS 侧高频故障装完提示nvm: command not found。九成是配置写错了文件。zsh 环境里你把那三行写进了~/.bash_profile那当然不生效。正确的位置是~/.zshrc。判断方法echo $SHELL看当前 shellls -la ~ | grep -E zshrc|bash_profile看有哪些文件。还有一种可能是配置写对了但当前窗口没重载source ~/.zshrc或者干脆开新窗口。nvm ls-remote卡住不动或者超时。这个命令要去远程拉版本清单网络不理想就会卡。临时方案是换个下载源通过环境变量指定export NVM_NODEJS_ORG_MIRROR可用的公共分发地址写进~/.zshrc里长期生效。注意这个变量只影响 Node 二进制的下载npm 包的下载源是另一回事要在npm config set registry里单独设。node命令指向了系统里的老版本。用which -a node把所有匹配项都列出来如果/usr/local/bin/node排在~/.nvm/...前面说明系统那个版本还在抢位置。把/usr/local/bin/node和/usr/local/bin/npm删掉它们通常是指向../lib/node_modules的软链接或者用官方卸载脚本清干净。nvm use报权限错误。千万别加sudo nvm use。nvm 是当前用户级工具加 sudo 会以 root 身份改环境变量之后~/.nvm下的文件所有者变成 root后续所有操作都会因为没权限而失败。真的已经用 sudo 了用sudo chown -R $(whoami) ~/.nvm修回来。npm i -g报 EACCES。经典问题。正确解法是回退到用 nvm 管权限不要去改~/.npmrc里的prefix。那行配置虽然当时能解决报错但把版本隔离彻底破坏了后患无穷。检查方法就是前面说的npm config get prefix。5.2 Windows 侧高频故障nvm use报exit status 1: Access is denied或者系统错误 5。权限问题两种解法以管理员身份运行终端或者把软链接路径从C:\Program Files\nodejs换到用户目录下一劳永逸。还有个容易被忽略的原因——软链接目标正被占用。开着编辑器、终端、Docker 里跑着 node 进程都可能锁着那个目录。把相关进程全关掉再试。node命令找不到但 nvm 说 use 成功了。检查顺序nvm root看 nvm 装在哪儿 → 看那个目录下的settings.txt里path是什么 → 检查系统环境变量PATH里有没有这一条 → 检查环境变量NVM_HOME和NVM_SYMLINK是否都在。改完环境变量必须重开终端光refreshenv有时候不够。中文输出乱码。控制台代码页的问题。临时解决chcp 65001切到 UTF-8长期解决是在系统设置里把使用 Unicode UTF-8 提供全球语言支持打开注意这个选项会影响一些老程序的编码行为谨慎开。另一个土办法是把终端换成 Windows Terminal默认编码处理得好很多。nvm install下载到一半失败。换镜像源前面说过改settings.txt或nvm node_mirror。还有一种情况是杀毒软件把下载的压缩包拦了临时关掉或者加白名单再试。装完 nvm 发现之前全局装的工具全没了。第一次装 nvm 时把原版 Node 卸了原版装的那些全局包当然跟着没了。回不去了重新装吧。列一下清单npm ls -g --depth0这是在新环境里查当前有什么老清单只能靠自己的记忆或者以前的文档。这也是个教训——全局工具清单最好记在一个 dotfiles 仓库里。5.3 排查速查表现象最可能原因处理动作nvm命令不存在配置没加载 / 写错文件检查 profile 文件、重开终端、source一次nvm命令存在但不管用用./nvm.sh执行而不是source改成 source 加载切换成功但node -v没变PATH 里有更靠前的 nodewhich -a node找出来删掉新终端版本又变回去了没设default别名nvm alias default 版本全局包换版本就消失版本隔离的正常行为用reinstall-packages迁移npm i -g权限报错全局目录权限 / prefix 被改查npm config get prefix删掉 prefix 配置Windows 软链接失败权限不足或目标被占用管理员终端 / 关进程 / 换路径下载卡住或超时源不可达换镜像地址原生模块编译失败Node 版本与模块 ABI 不匹配切到项目要求的版本清 node_modules 重装6. 一点工程化经验团队与流水线里怎么用6.1 团队里怎么把版本约定下来.nvmrc只是个人便利光有它不够。真正能约束住团队的是三层配合.nvmrc给本地用、package.json的engines字段声明版本范围、lockfile 锁住依赖树。三层都对齐了新人克隆下来才有可能一次跑通。engines字段长这样{ engines: { node: 18.18.0 21, npm: 9 } }注意它的默认行为只是警告不会阻止安装。想让它真正拦截可以配engine-stricttrue但我不太建议一上来就开得这么硬——有些边缘依赖的 engines 声明写得很随意一开就全卡住了。折中方案是在 preinstall 钩子里自己判断一下版本给个明确的中文提示。另外一个实操细节.nvmrc里写的版本号最好和 CI 里用的完全一致包括小版本号。写18让 nvm 自己解析不同人机器上装的最新 18.x 可能不一样某些依赖的行为在小版本之间确实会变。6.2 流水线环境的处理CI 环境里没必要装 nvm。流水线容器每次都是全新的直接指定版本更直接。常见的两种做法一种是用环境自带的多版本管理能力在流水线的 setup 步骤里声明要哪个版本跑之前把版本装上并设为当前。另一种是干脆让容器镜像自带固定版本。这种做法最省心——镜像构建时就决定了版本运行时没有任何版本切换的变数。缺点是升级版本要重建镜像周期长一点。不管哪种都建议在流水线的第一步加一句node -v打印出来写进日志。我吃过这个亏某次本地一切正常CI 上挂了查了一下午才发现流水线偷偷换了个默认 Node 版本跟.nvmrc里写的隔了一个大版本。日志里有一行版本号两秒就能定位。还有一种情况要提防缓存。很多流水线会缓存~/.npm或者node_modules来加速缓存 key 应当包含 Node 版本号。否则换了版本但缓存没失效装出来的原生模块跟当前 Node 的 ABI 对不上报错信息会非常莫名其妙。6.3 什么时候该考虑换掉 nvmnvm 不是唯一选择它胜在资历老、文档多、坑都被人踩过了。但它在两个场景下确实有短板。一是启动速度。POSIX 版 nvm 是纯 shell 脚本每次开终端都要执行一遍函数定义机器慢的时候能感知到几十上百毫秒的延迟。有些新工具用编译语言实现启动快一个数量级切换版本也更快。二是跨平台一致性。前面说了 macOS 和 Windows 用的是两套东西命令、行为、.nvmrc支持度都有差异。如果团队里两种系统都有想统一体验的话找那些用同一份代码支持全平台的工具会省不少沟通成本。还有一类做法是把版本管理下沉到项目层面结合engines字段自动帮你切连手动nvm use都省了。我的态度是已经装好 nvm 并且用着没问题的别折腾。工具换来换去的成本远大于那点启动延迟。只有当你在多系统协作、或者终端启动速度真的影响到日常体验时才值得花半天迁移一次。6.4 我个人的几条习惯最后分享几个实操细节都是吃过亏总结出来的。~/.nvm目录我会定期看一眼大小du -sh ~/.nvm。这地方很容易膨胀到十几 G因为每个版本都带完整的一套 npm 和全局包。长期不用的版本及时nvm uninstall清掉尤其是那些装过一次就没再用过的奇偶版本。装新版本之前先在项目里cat .nvmrc确认一下目标版本别凭印象敲。我干过好几次以为项目在 18 上实际锁的是 16的事删了 node_modules 重装半天才发现压根不用切。还有一条任何时候都别把 nvm 和别的 Node 管理工具混用在同一台机器上。两个工具互相覆盖 PATH、抢同一个目录报错信息还互相矛盾排查成本极高。用哪个就一路用到底要换就彻底卸干净再换。真遇到实在搞不定的环境问题最后一招是去看 nvm 自己的诊断输出。POSIX 版有nvm debug会把 shell 类型、PATH、各关键环境变量、目录权限全打印出来绝大多数配置类问题看一眼就清楚了。比在网上翻半天帖子快得多。