你在 Windows 上用 Node.js 开发,最烦的事莫过于版本切换。项目 A 要 Node 16,项目 B 要求 Node 20,系统里装的是 Node 18,然后你满世界找卸载重装教程,最后还把环境变量搞乱了。这不是技术问题,这是工具选型问题。我在踩过 nvm-windows 的坑、用过 Volta 之后,最后停在 Fnm(Fast Node Manager)上,至今没再换过。
Fnm 是 Rust 写的 Node.js 版本管理器,跨平台支持 Windows、macOS、Linux,核心特点就一个字:快。它通过符号链接和全局目录管理多个 Node 版本,切换速度是毫秒级,而且跟 PowerShell、CMD、Git Bash 都配合得很好。这篇文章我会把 Windows 下的 Fnm 安装、配置、日常使用和踩坑全过程写清楚,你照着做就能搞定,不需要再去看那些碎片化教程。
1. 为什么选择 Fnm,而不是 nvm-windows 或 Volta
1.1 Windows 上 Node 版本管理的痛点
Windows 不是类 Unix 系统,没有 /usr/local/bin 这种全局软链体系,所以很多在 macOS 和 Linux 上很顺手的 Node 版本管理工具,在 Windows 上要么水土不服,要么功能残缺。nvm-windows 是最多人用的方案,但它的设计是从 nvm 移植过来的,跟 Windows 的进程模型配合得并不好,经常出现切换版本后 node -v 还是旧版本、npm 全局包路径错乱的问题。
更麻烦的是 nvm-windows 的安装和卸载都涉及注册表和环境变量,一旦半路出错,系统里残留的 Node 配置会干扰后续一切操作。我见过很多同事的电脑上 node 还是能跑,但 npm 已经指向了一个不存在的路径,或者 where.exe node 返回了多个结果,这时候你根本不知道当前实际生效的是哪个 Node。
Volta 是另一个热门方案,它的设计思路是"工具链即代码",把 Node 版本身份绑定到项目里,体验很新颖。但它的问题在于下载源固定走官方渠道,在大陆网络环境下经常慢得让人抓狂,而且它的全局工具链管理方式比较激进,不一定符合每个人的习惯。我更倾向那种"我想切哪个版本就切哪个版本"的自由度,Fnm 正好是这种风格。
1.2 Fnm、nvm-windows、Volta 关键对比
| 对比项 | Fnm | nvm-windows | Volta |
|---|---|---|---|
| 底层实现 | Rust 原生 | Go 移植版 | Rust 原生 |
| 安装方式 | winget / scoop / 手动 exe | 安装包 + 管理员权限 | 安装包 |
| 切换速度 | 毫秒级 | 秒级 | 毫秒级 |
| 项目级版本 | 支持 .nvmrc | 支持 .nvmrc | 支持 package.json + 锁定 |
| 下载源配置 | 支持镜像覆盖 | 不灵活 | 官方源为主 |
| 全局包隔离 | 不隔离 | 不隔离 | 隔离 |
| Windows 兼容性 | 好 | 一般 | 好 |
从表格里能看出来,Fnm 在 Windows 上最大的优势是下载源可配置和切换速度快。Rust 写的工具在进程调用、文件操作方面天生高效,fnm use 本质上是把当前目录的一个符号链接指向对应 Node 版本目录,这个操作在 Windows 上虽然受到 NTFS 权限限制,但 Fnm 封装得很好,使用体验跟 Linux 上几乎没有差别。
1.3 Fnm 的工作原理简析
Fnm 的核心思路是"一个全局目录存所有版本,一个入口链接指当前版本"。它会在你的用户目录下创建一个存储目录,默认位置是 %APPDATA%\fnm(如果你设置了环境变量 FNM_DIR 则按其路径),所有通过 fnm install 下载的 Node.js 版本都放在这个目录的 node-versions 子目录下。
当你执行 fnm use 20.11.1 时,Fnm 会创建(或更新)一个符号链接,把这个链接指向 node-versions/v20.11.1/installation。然后这个链接的目录会被写入当前用户的 PATH 环境变量最前面,所以你在终端里敲 node 或 npm 时,系统会先找到这个链接,再找到对应版本。
这个设计有两个好处:一是切换版本不需要修改系统级的 PATH,只改当前终端会话的 PATH(通过 fnm env 注入),所以不会污染全局环境;二是卸载版本只需要删目录和重建链接,不会留下乱七八糟的残留文件。理解了这一点,后面遇到"为什么我版本没切换成功"这类问题时,第一反应就应该是"看看 PATH 里有没有多余的东西"。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. Windows 下安装 Fnm 的三种方式
2.1 用 winget 安装(推荐)
如果你用的是 Windows 10 1809 以上或 Windows 11,系统自带 winget 包管理器,这是最省事的安装方式。在 PowerShell 或 CMD 里执行:
powershell复制winget install Schniz.fnm
装完后,winget 会自动把 Fnm 的可执行文件路径加到用户 PATH 里,但你当前已打开的终端窗口不会立即生效,需要新开一个终端窗口,或者执行 refreshenv(如果装了 Chocolatey 的话)。
有个细节要注意:winget 安装的 Fnm 版本可能不是最新版,因为包仓库更新有延迟。如果你对版本有强迫症,安装后可以执行 fnm --version 看看,再去 GitHub Releases 页面核对一下。我在一次实测中遇到过 winget 仓库落后两个小版本的情况,虽然不影响使用,但新功能可能没有。想获取最新版就直接看下面手动安装的方式。
2.2 用 scoop 安装
如果你平时用 scoop 管理 Windows 软件,那 Fnm 的安装更简单:
powershell复制scoop install fnm
scoop 的优点是会把软件安装到统一的目录(默认 USERPROFILE\scoop\apps\fnm\current),更新也方便,scoop update fnm 一条命令搞定。而且 scoop 安装的软件不需要管 PATH,scoop 自己会处理。对于像我这样习惯用 scoop 管理开发工具的人来说,这种方式最顺手。
不过 scoop 也有个潜在问题:它默认使用 Git 来拉取软件清单,如果你系统里没装 Git,scoop 本身都跑不起来。所以如果你不想为了装 Fnm 再去装一堆依赖,直接用 winget 就好。
2.3 手动下载 exe 安装
Fnm 的 GitHub Releases 页面提供 Windows 的 zip 包,里面是一个 fnm.exe。你可以把它放到一个方便的位置,比如 D:\tools\fnm\,然后把这个目录加入用户 PATH。
这种方式最适合"不想依赖任何包管理器"或者"公司电脑网络受限装不了 winget"的场景。具体步骤如下:
- 打开 GitHub Releases 页面,下载
fnm-windows.zip文件。 - 解压到目标目录,比如
D:\tools\fnm,确保目录下能看到fnm.exe。 - 按
Win + R输入sysdm.cpl打开系统属性(或直接搜索"编辑账户的环境变量"),在用户变量里找到Path,点击编辑,新增一行D:\tools\fnm。 - 确定保存后,新开终端,执行
fnm --version验证。
手动安装时,存放路径不要带空格和中文。Fnm 虽然对路径兼容性做得不错,但有些 Node 工具在带空格的路径下会有奇怪问题,别给自己找麻烦。
3. Shell 集成配置:让 fnm 在终端里自动生效
3.1 PowerShell 配置(Windows 默认终端)
Fnm 安装完成后,直接在终端里敲 fnm 是会提示找不到命令的,因为它还需要做"Shell 集成"这一步。打开 PowerShell 配置文件:
powershell复制notepad $PROFILE
如果提示"找不到此文件",先创建目录和文件:
powershell复制New-Item -ItemType Directory -Force $PROFILE\..
New-Item -ItemType File -Force $PROFILE
notepad $PROFILE
在文件里加一行:
powershell复制fnm env --use-on-cd | Out-String | Invoke-Expression
保存关闭,重新打开终端,或者执行 . $PROFILE 让配置立即生效。这一步的作用是:每次启动 PowerShell 时,加载 Fnm 的环境变量注入逻辑,设置好当前会话的 PATH,并注册一个目录切换监听事件,让你 cd 到带 .nvmrc 的目录时自动切换对应 Node 版本。
--use-on-cd 这个参数很关键。不加它,你每次进项目目录后还要手动执行 fnm use;加了它,Fnm 会在你 cd 到某个目录时自动检测该目录下的 .nvmrc 文件,如果存在就切换,不存在就保持当前版本。这个体验跟 nvm 的 autoload 一样,不用手动干预。
3.2 CMD 配置
如果你偶尔会用 CMD 窗口操作,需要让 Fnm 在 CMD 里也能用。在 CMD 里执行一次:
cmd复制fnm env --use-on-cd | iex
但这个只对当前窗口有效,想要永久生效,需要设置注册表或者创建一个 autorun.cmd。更简单的做法是直接用 cmd /k "fnm env --use-on-cd | iex" 来启动 CMD,或者干脆把 CMD 里的操作都放到 PowerShell 里做。
说实话,如果你主要在 Windows 上开发,我建议尽快切到 PowerShell 或 Windows Terminal + PowerShell。CMD 对 Fnm 的支持虽然能用,但体验会打折,尤其是终端提示符的渲染和 Tab 补全能力差了一大截。
3.3 Git Bash 配置
很多项目在 Windows 上还是要操作 Git Bash,Fnm 也支持 Git Bash。打开 Git Bash 配置文件:
bash复制echo 'eval "$(fnm env --use-on-cd)"' >> ~/.bashrc
source ~/.bashrc
注意 Git Bash 的 fnm 命令是小写的,它的 env 输出的是 bash 语法,所以在 Git Bash 里用 eval,在 PowerShell 里用 Invoke-Expression,这个区别不能搞混。
我在实际使用中发现,Git Bash 下 Fnm 的路径切换偶尔会慢一点,因为 Git Bash 自己有一套 POSIX 路径转换逻辑,fnm 生成的 Windows 路径在 bash 里可能需要转换。后来我干脆在 Git Bash 里只用 fnm exec --using=<版本> <命令> 这种方式跑单条命令,比如 fnm exec --using=16 npm install,避免全局切换对 bash 环境的干扰。
3.4 验证集成是否成功
配置完成后,新开一个终端,依次执行:
powershell复制fnm --version
fnm list
如果 fnm list 能显示空列表或已有的版本列表,说明 Fnm 本身没问题。再执行:
powershell复制fnm install --lts
fnm use lts-latest
node -v
如果 node -v 输出了对应的 LTS 版本号,说明 Shell 集成成功了。这里我再额外提醒一个检查点:执行 Get-Command node | Format-List Source,看看 node 的实际路径是否指向 Fnm 的链接目录。如果指向了系统盘里原来的旧 Node 路径,说明你以前的 Node 安装还没清干净,后面我会专门说这个问题。
4. 核心命令与日常使用技巧
4.1 安装与切换 Node 版本
Fnm 最常用的命令就是 install 和 use。安装指定版本:
powershell复制fnm install 18.20.8
安装最新的 LTS 版本:
powershell复制fnm install --lts
安装完成后切换版本:
powershell复制fnm use 18.20.8
查看当前生效版本:
powershell复制fnm current
查看全部已安装版本:
powershell复制fnm list
这里我解释一下 fnm list 的输出格式。执行后你会看到类似这样的内容:
text复制* v16.20.2
* v18.20.8
* v20.11.1
* v22.14.0
带 * 的表示已安装。别跟我一开始一样以为 * 是当前使用版本,它表示"这个版本已经安装到本地"。当前版本的标记是 fnm current 的输出,或者你在执行 fnm use 时它提示你切到了哪个版本。
4.2 .nvmrc 文件与项目级版本管理
--use-on-cd 让我们可以做到"进入项目目录自动切版本",前提是项目根目录有这个 .nvmrc 文件:
text复制20.11.1
或者:
text复制lts/iron
如果你的项目还没有 .nvmrc,可以用 fnm 配合 node 快速生成:
powershell复制node -p "process.version.slice(1)" > .nvmrc
这句话的意思是:node -p 会输出当前 Node 版本号(比如 v20.11.1),slice(1) 去掉 v 前缀,重定向写入 .nvmrc 文件。以后任何人进入这个项目,只要他的 Fnm 开了 --use-on-cd,就会自动读到 20.11.1 并切换版本。如果本地没有装这个版本,Fnm 会提示你先执行 fnm install。
使用 .nvmrc 的时候还有个小坑:如果你的 .nvmrc 写的是 20,Fnm 会把它当版本前缀处理,安装 20.x.x 的最新版;但如果你想要精确锁版本,必须写完整的三段式版本号,比如 20.11.1。团队协作时,我建议 .nvmrc 必须写完整版本号,否则不同人电脑上安装出来的 Node 补丁版本可能不一致,这在某些依赖原生模块的项目里会引发不可复现的 bug。
4.3 别名、默认版本与卸载
长时间用一个版本时,可以给它设个默认值,以后新开终端就自动用这个版本,不用手动 use:
powershell复制fnm default 20.11.1
如果你有两三个常用版本,可以起个别名方便记忆:
powershell复制fnm alias 18.20.8 lts-18
fnm use lts-18
查看已有别名:
powershell复制fnm list-aliases
卸载不要的版本:
powershell复制fnm uninstall 16.20.2
如果你要彻底清空所有版本重来,直接删除 %APPDATA%\fnm\node-versions 目录下的所有子目录即可,不会影响 Fnm 本体。这个方法在排查异常版本时特别管用,比 fnm uninstall 一个个卸快多了。
4.4 常用配置项和全局参数
Fnm 支持一些环境变量和配置项,如果默认设置满足不了你,可以在系统环境变量里加:
| 环境变量 | 作用 | 我的推荐值 |
|---|---|---|
FNM_DIR |
设置 Fnm 数据存储目录 | D:\fnm(放非系统盘) |
FNM_NODE_DIST_MIRROR |
设置 Node 下载镜像 | 国内可配 https://npmmirror.com/mirrors/node/ |
FNM_COREPACK_ENABLED |
启用 corepack | true(如果你用 yarn/pnpm) |
FNM_MULTISHELL_PATH |
指定多终端共享的链接路径 | 一般不用设 |
FNM_LOGLEVEL |
输出日志级别 | info |
这里特别说一下 FNM_NODE_DIST_MIRROR。默认 Fnm 从 Node 官网下载二进制包,在大陆网络环境下很慢,经常超时。设置镜像后:
powershell复制[Environment]::SetEnvironmentVariable("FNM_NODE_DIST_MIRROR", "https://npmmirror.com/mirrors/node/", "User")
设置后新开终端,再执行 fnm install 20.11.1,下载速度会有质的提升。npm 的镜像可以在用户级 .npmrc 里配:
ini复制registry=https://registry.npmmirror.com
用镜像的时候注意版本一致性,Node 二进制包的镜像和 npm 包的镜像最好指向同一家服务商,避免某个包在 A 镜像有缓存、在 B 镜像没有的情况。
5. 常见问题与排查技巧实录
5.1 "fnm 不是内部或外部命令"怎么办
这是 Windows 下最常见的报错。原因就一个:fnm.exe 所在目录不在 PATH 里。你可以先执行 where.exe fnm 看能不能找到,如果输出为空,说明 PATH 没配好。
如果确定已经加了 PATH 但还是不行,先检查是不是在"用户变量"里改的(有些安装程序改的是"系统变量",但你的当前用户没权限读)。改完 PATH 后必须新开终端,不要在当前窗口里反复验证。Windows 终端的 PATH 是在启动时加载的,不会实时更新,这是很多人的认知误区。
如果你用的是 winget 安装,但 fnm 还是找不到,可以去微软商店的安装日志里看看有没有成功。有时 winget 下载会失败但没报错,然后你 PATH 里确实没有,最后只能用手动安装兜底。
5.2 PowerShell 执行策略限制
新增 fnm env --use-on-cd | Out-String | Invoke-Expression 到 $PROFILE 后,新开终端报错:
text复制无法加载文件 C:\Users\xxx\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1,因为在此系统上禁止运行脚本。
这是 PowerShell 的执行策略(Execution Policy)默认是 Restricted 导致的。解决办法:
powershell复制Set-ExecutionPolicy -Scope CurrentUser RemoteSigned
RemoteSigned 表示本地创建的脚本可以运行,从网络下载的脚本需要有签名。这个策略级别是开发者的常规配置,不会带来额外的安全风险,因为你的 PowerShell 配置都是自己写的。如果你在公司电脑上受组策略限制改不了,那就换用手动安装方式,把 fnm.exe 装好后只在 CMD 里用,或者通过 cmd /k call fnm env | iex 绕过。
5.3 切换版本后 node -v 还是旧版本
这个问题一般是两个原因:一是系统里有旧版 Node 的安装目录还在 PATH 里,且优先级比 Fnm 的链接目录高;二是当前终端会话的 PATH 没刷新。
先看 where.exe node 的输出,如果出现了多个路径,说明 PATH 里有多个 node.exe。你需要检查用户变量和系统变量里的 Path,把旧 Node 的安装目录(比如 C:\Program Files\nodejs)删掉。Fnm 的链接目录默认是 %APPDATA%\fnm\aliases\default 或 %APPDATA%\fnm\multishells 里的某个路径,它在设置 PATH 时会放在最前面,按 Windows 的查找顺序,右边先匹配,但 Fnm 注入的那条是当前会话最前面的,所以正常情况下应该优先命中。
如果删掉旧 Node 目录后还是有问题,干脆把这个目录清空或者卸载掉。Windows 上同时存在两套 Node 是万恶之源,这类问题一半以上都是它引起的。
5.4 Fnm 报 "xxx is not yet released or is not available"
有些时候你执行:
powershell复制fnm install 24.19.0
它提示 error installing 24.19.0: node.js v24.19.0 is not yet released or is not avail...,这个报错的意思是:你输入的这个版本号在官方版本列表里不存在。可能是你打错了版本号,或者是版本号超前了(你刚在某个地方看到了新的 RC 版本号,但官方还没正式发布),也可能是你的镜像源没有同步到这个版本。
解决办法是先看官方版本列表:
powershell复制fnm list-remote
如果 list-remote 拉取失败或者看不到新版本,多半是镜像源的问题。临时去掉 FNM_NODE_DIST_MIRROR 环境变量,用官方源试一次,或者反过来,用镜像源试试。出现这种问题时,我的经验是:版本列表以 fnm list-remote 的输出为准,不要凭记忆输版本号。
5.5 版本下载慢或卡住
Fnm 默认从 Node 官网下载,速度可能让你怀疑人生。设置 FNM_NODE_DIST_MIRROR 后一般能解决。但如果你已经设置了镜像还是慢,检查一下环境变量是否真的生效:
powershell复制echo $env:FNM_NODE_DIST_MIRROR
如果输出为空,说明你没设置成功,或者当前会话没刷新。设置用户级环境变量后要新开终端才能加载。
另外一个冷门但真实存在的情况是:公司网络只放行特定域名,Fnm 的下载请求被防火墙拦截了。这时候你可以考虑在浏览器/下载工具里手动下载 Node 安装包,然后放到 %APPDATA%\fnm\node-versions\v20.11.1\installation 目录下,再手动建好符号链接。这个方案比较手动挡,不推荐日常用,但应急还行。
5.6 与 nvm-windows 的冲突问题
如果你以前用过 nvm-windows,现在切到 Fnm,一定要把 nvm-windows 卸载干净。我见过最典型的情况是:nvm-windows 的符号链接在 C:\Program Files\nodejs,Fnm 创建的链接在另一个目录,两者同时存在时,where.exe node 会出现多个结果,结果一会儿生效这个一会儿生效那个,项目构建时出现诡异的错误。
手动清理步骤:
- 卸载 nvm-windows(通过控制面板或
nvm uninstall)。 - 删除
C:\Program Files\nodejs这个目录里残留的符号链接或文件。 - 检查系统 PATH,删掉所有跟 nvm-windows 相关的目录。
- 重新打开终端,执行
where.exe node确认只有一个路径,且指向 Fnm 的链接目录。
如果你以前还手动装过 Node 安装包,记得在"控制面板-程序和功能"里卸载它,因为 MSI 安装的 Node 会写入注册表的卸载信息,不卸干净的话 node -v 有时候会拿到一个残留版本。
6. 团队协作和 CI 场景下的额外建议
Fnm 不只适合个人电脑,在团队里配合 .nvmrc 和 PowerShell 配置,效果很统一。我给团队做前端基建时,会要求每个人必须装 Fnm(通过 winget 或 scoop),并在项目仓库根目录放 .nvmrc。然后统一在每个成员的 $PROFILE 里加 fnm env --use-on-cd。这样全团队 Node 版本完全一致,不用再出"我本机跑得好好的"这种甩锅梗。
如果你负责 CI/CD,也可以在 GitLab CI 或 GitHub Actions 的 Windows Runner 上用 Fnm。GitHub Actions 官方维护了 actions/setup-node,但如果你本身就想在 CI 里测试多个 Node 版本的矩阵,用 Fnm 更统一。在 GitHub Actions 里可以这样:
yaml复制- name: Install fnm
run: winget install Schniz.fnm
- name: Setup Node
shell: pwsh
run: |
fnm env --use-on-cd | Out-String | Invoke-Expression
fnm install
fnm use
配合 .nvmrc,fnm install 会读取项目里的版本,然后 fnm use 会切换到对应版本,整个流程简单直接。
还有个容易被忽略的点:Fnm 本身可以通过 fnm completions 生成终端补全脚本。PowerShell 下可以执行:
powershell复制fnm completions --shell power-shell | Out-String | Invoke-Expression
把这句话加到 $PROFILE 里,以后敲 fnm i 按 Tab 就能补全出 install,敲 fnm use 按 Tab 能列出已安装版本,效率提升不少。我实测下来,这个补全在 Windows Terminal 里的表现很稳定。
7. 我个人踩坑后的配置清单
如果你不想看前面的长篇分析,直接拿这最后一套"作业"去配就好。我在多台 Windows 机器上验证过这套配置,从零到可用大概五分钟。
- 用 winget 安装 Fnm:
winget install Schniz.fnm。 - 新开终端,设置用户级镜像:
powershell复制[Environment]::SetEnvironmentVariable("FNM_NODE_DIST_MIRROR", "https://npmmirror.com/mirrors/node/", "User")
- 确保 PowerShell 执行策略为
RemoteSigned。 - 在
$PROFILE里追加:
powershell复制fnm env --use-on-cd | Out-String | Invoke-Expression
fnm completions --shell power-shell | Out-String | Invoke-Expression
- 新开终端,安装你需要的 Node 版本:
powershell复制fnm install --lts
fnm use lts-latest
-
在项目根目录创建
.nvmrc,内容写你锁定的版本号。 -
检查旧 Node 残留:
where.exe node,确保只有一个路径。
这套配置搞定后,你再也不需要手动切换 Node 版本,也不用担心把系统搞脏。Fnm 最大的好处就是它把自己隔离在用户目录里,出了问题直接删目录重来,系统级的东西一点不动,这对 Windows 开发者来说是救命级的体验。
