搞 Node.js 开发的人,迟早会撞上一个尴尬场景:电脑里装的是 Node 16,但公司老项目跑在 14 上,新项目又要用 20 的 LTS;或者刚把某个依赖升级完,项目一启动就是各种 "a later version of node.js" 的报错。以前我也用过手动改 PATH 这种笨办法,每次切版本都要重新下安装包、改环境变量、重启终端,折腾十来分钟,项目跑起来却还是旧版本。后来换到 fnm(Fast Node Manager)之后,这类问题基本就消失了。这是一款用 Rust 写的 Node.js 版本管理工具,在 Windows 上安装和配置都相当顺手,切换版本几乎是瞬间完成。这篇文章就把我在 Windows 上安装、配置、使用 fnm 的完整过程记录下来,包括踩过的坑和排查思路,给刚开始接触版本管理的朋友一个可以直接照做的参考。
1. 为什么需要 Node.js 版本管理工具,fnm 到底解决了什么问题
1.1 Node.js 版本切换的现实痛点
很多人第一次接触 Node.js 时会有个疑惑:这工具到底是干什么的?简单说,Node.js 是一个让 JavaScript 脱离浏览器运行的环境,前端工程化、服务端接口、各种自动化脚本都会用到它。问题在于,Node.js 的版本迭代非常快,每个大版本都会调整 API、改变依赖的编译行为,比如某些老旧的 C++ 扩展在 Node 18 上直接编译失败,某些框架在 Node 20 之前根本不支持。这时候如果电脑里只有一个版本的 Node,就会陷入"装新项目就得卸老项目"的死循环。
我见过最典型的例子:同事在 Node 14 环境里维护一个已经上线多年的老系统,同时又要开发一个基于最新框架的新服务。因为只有一个 Node,他只能频繁卸载重装,在 14 和 22 之间来回切换,中间还出现过一次卸载不干净导致 npm 全局包全部丢失的情况。这个问题不是个例,只要是长期做 Node 开发的人,基本都会遇到多版本共存的硬需求。
1.2 fnm 和 nvm-windows 怎么选
提到 Windows 上的 Node 版本管理,绕不开 nvm-windows,它在国内教程里出现频率很高。但我个人用过一段时间后,体验只能说一般。它是命令行交互式的,每次切换版本要敲菜单,脚本执行速度也不算快;而且它通过修改系统 PATH 来实现切换,偶尔会出现版本"假切换"的情况,明明提示切换成功了,node -v 还是旧版本。
fnm 的优势主要体现在三个方面:
- 快:本身用 Rust 编写,安装版本、切换版本的速度比 nvm-windows 快一个量级,实测切换命令几乎感觉不到延迟。
- 自动切换:支持识别 .nvmrc 和 .node-version 文件,进入项目目录时自动切到对应版本,省去手动操作。
- 跨平台:在 macOS 和 Linux 上同样能用,以后换电脑或者用 WSL 时不需要重新学习一套工具。
如果你已经在用 nvm-windows 且没有遇到任何问题,倒也不必强行换;但如果你正在为多版本切换头疼,或者在寻找一个更顺手的工具,fnm 值得投入十分钟试试。
1.3 fnm 的核心工作原理:一个符号链接是怎么做到瞬间切换的
fnm 装好之后,会在你的用户目录下建立一个存放所有版本的文件目录,每个安装过的 Node.js 版本都完整保存在那里。它并不会像传统安装包那样把 Node 写进系统目录,而是通过符号链接(Symlink)指向当前正在使用的版本目录。
你的 PATH 环境变量里添加的是这个符号链接的路径,而不是某个具体版本的路径。当你执行 fnm use 20.11.1 时,fnm 只是把这个符号链接重新指到 20.11.1 对应的目录上,整个切换过程就是替换一个快捷方式指向,所以速度极快。这也是为什么用它切换版本不需要重启电脑、不需要刷新环境变量,当前终端里立刻生效。
想验证这一点也很简单:在终端里执行 fnm install 20.11.1 装好版本后,找到 fnm 的安装目录(一般在 %AppData%\fnm 或者你自定义的目录下),能看到 node-versions 之类的文件夹里按版本名字存放着目录,而当前正在用的那个版本其实就是个链接。理解了这一层,后面遇到"切换不生效"类问题,排查思路就清晰多了。另外值得留意的是,fnm 会为每个终端会话生成独立的链接路径,所以你在 A 终端用 Node 16、在 B 终端用 Node 20 是完全可行的,这在处理并行项目的场景里非常实用。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. Windows 安装 fnm:从零到能用的完整流程
2.1 安装前的环境检查与准备
在动手之前,建议先打开一个 PowerShell 窗口,检查三件事:
- Windows 版本是否支持符号链接:一般来说 Win10 1703 之后的版本都没问题,老版本系统需要确认开发者模式是否开启。
- 是否已经安装过 Node.js:如果已经装了,建议先把旧版本卸载干净,否则 PATH 里可能残留旧的 node 路径,和 fnm 冲突。
- PowerShell 执行策略:fnm 的 shell 集成脚本需要当前用户有执行脚本的权限。如果之前没动过,执行下面的命令可以把执行策略改为当前用户级别的 RemoteSigned,只影响你自己的账户,安全风险可控。
powershell复制Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
这一步不算必须,但如果后面执行 fnm env 时提示"禁止运行脚本",回来先把这条命令跑掉。
提示:这条命令只修改当前用户的执行策略,不影响系统其他用户,也不会降低系统整体安全级别。运行后如果策略查询结果显示 RemoteSigned,说明已经生效。
2.2 三种安装方式,推荐用 winget
fnm 在 Windows 上提供了多种安装方式,我分成三类说一下,大家按自己习惯选。
方式一:winget 安装(最推荐)
powershell复制winget install Schniz.fnm
winget 是 Windows 自带的包管理器,Win11 和较新的 Win10 系统都内置了。它会自动下载安装包,写入 PATH,整个过程不需要管理员权限,装完直接开新终端就能用。如果你不确定系统有没有 winget,可以先执行 winget --version 确认。
方式二:Scoop 安装
powershell复制scoop install fnm
Scoop 是很多开发者喜欢用的社区包管理器,优点是安装的软件都在用户目录下,不需要管理员权限,卸载也干净。如果你已经在用 Scoop 管理软件,直接一条命令即可。
方式三:官方脚本安装
powershell复制irm fnm.vercel.app | iex
这是一条从官方地址拉取脚本并执行的命令,适合特殊场景。我一般不太推荐直接用网页脚本,因为执行前你没法看到脚本内容,万一哪天发布方账户被接管就比较危险。想用这个方式的话,建议先打开 fnm 的 GitHub 仓库,找到 install.ps1 脚本内容看完再执行。
2.3 配置环境变量与 PATH
winget 和 Scoop 安装完一般会自动写入 PATH,但通过其他方式安装或者安装后新终端里找不到 fnm 命令时,需要手动检查配置。
fnm 相关的两个环境变量:
- FNM_DIR:fnm 存放所有 Node.js 版本的目录,默认是 %AppData%\fnm,一般不需要改。
- FNM_NODE_DIST_MIRROR:Node.js 二进制文件的下载镜像地址,在国内网络环境下,建议设置成镜像源,能明显提升安装速度,后面章节会详细说。
如果打开新终端仍然提示"fnm 不是内部或外部命令",按 Win 键搜索"编辑系统环境变量",打开环境变量窗口,在用户变量 Path 里确认是否有 fnm 的路径。没有就手动添加。winget 安装的路径一般是 %LOCALAPPDATA%\Microsoft\WinGet\Links,Scoop 则是 %USERPROFILE%\scoop\shims。加完之后记得重启终端,环境变量的变更不会自动加载到已经打开的窗口里。
3. fnm 核心配置与常用命令速查
3.1 初始化 shell 集成:让每个新终端都能用 fnm
安装完 fnm 只是第一步,如果不做 shell 集成,你会发现每次打开新终端都要手动执行一次 fnm env 才能使用命令,非常别扭。正确做法是把 fnm 的初始化脚本写入到你默认 shell 的配置文件里。
PowerShell 用户,在终端里执行:
powershell复制fnm env --use-on-cd | Out-String | Invoke-Expression
这条命令能让你当前窗口立刻生效,但它只是临时的。把它写进 PowerShell 配置文件的命令如下:
powershell复制notepad $PROFILE
如果提示没有这个文件,先执行:
powershell复制New-Item -ItemType File -Path $PROFILE -Force
再打开编辑,在文件末尾追加一行:
powershell复制fnm env --use-on-cd | Out-String | Invoke-Expression
保存后,未来每个新开的 PowerShell 窗口都会自动加载 fnm 环境,并且支持进入目录时自动切换 Node 版本。
Git Bash 用户,在 ~/.bashrc 或 ~/.bash_profile 里追加:
bash复制eval "$(fnm env --use-on-cd --shell bash)"
这里的关键参数是 --use-on-cd,它的作用是让 fnm 在每次切换目录时自动检测 .nvmrc 或 .node-version 文件并切换对应版本。如果你不想要自动切换,只想手动控制,去掉这个参数即可。
3.2 常用命令速查表
把命令整理成表格,方便大家贴在工位上:
| 命令 | 作用 |
|---|---|
| fnm list / fnm ls | 列出本地已安装的所有 Node 版本 |
| fnm list-remote | 列出远端可用版本,数量很多时可配合 grep 过滤 |
| fnm install --lts | 安装最新的 LTS 长期维护版 |
| fnm install 20.11.1 | 安装指定版本,版本号必须精确到三位 |
| fnm use 20.11.1 | 在当前终端切换到指定版本 |
| fnm default 20.11.1 | 设置默认版本,新终端打开时自动使用 |
| fnm uninstall 20.11.1 | 卸载指定版本 |
| fnm current | 查看当前终端正在使用的版本 |
| fnm alias 20.11.1 myapp | 给指定版本设置一个自定义别名 |
| fnm exec --using=20.11.1 node app.js | 用指定版本临时执行某条命令,不切换环境 |
这张表里最常用的其实只有 fnm install、fnm use、fnm default 三个。先把这三个用熟,其他命令遇到具体场景再翻表也不迟。fnm exec 那个能力特别适合某些"我只想用特定版本跑一次脚本,但不想改当前环境"的场景,比如定时任务里固定用 Node 18 跑一个老脚本,其他地方继续用 Node 20,互不干扰。
3.3 使用 .nvmrc 和 .node-version 实现目录自动切换
这是 fnm 里我最喜欢的功能。在项目根目录下创建一个 .node-version 文件(或者老项目常见的是 .nvmrc),里面写上项目需要的 Node 版本,比如:
code复制20.11.1
如果你不强制锁定小版本,也可以写:
code复制20
这样进入项目目录时,fnm 只要检测到该文件,就会自动切换到 20.x 的最新已安装版本;如果本地没装对应版本,fnm 还会提示你执行 fnm install 来安装。团队协作时,把 .node-version 提交到 Git 仓库,所有成员进来就自动处于同一版本环境,从源头上消灭"我本地跑得好好的啊"这类问题。
实际踩坑提醒:.node-version 文件里不要有多余的空格或换行符,尤其不要在文件末尾加一个看不见的 BOM 头,否则 fnm 可能识别失败。遇到自动切换不生效时,优先检查文件内容是否干净。
3.4 设置下载镜像,解决安装慢的问题
fnm 在安装 Node 版本时,默认从 Node 官方源下载二进制包。在国内网络环境下,这个下载速度经常看心情,有时候二十多兆的压缩包能卡十分钟。解决办法是设置环境变量 FNM_NODE_DIST_MIRROR,指向国内可用的镜像地址。
在 PowerShell 里执行:
powershell复制[Environment]::SetEnvironmentVariable("FNM_NODE_DIST_MIRROR", "https://npmmirror.com/mirrors/node/", "User")
执行完这条命令后,新开的终端就会生效。把镜像地址替换成你常用的源即可。设置之后,fnm list-remote 也会读这个镜像的索引,所以版本列表的加载速度也会变快。
4. 实操过程:从零搭建一套干净可用的 Node.js 环境
4.1 完整安装演示:新电脑从一个空壳到能跑项目
为了让大家少走弯路,我把从零开始的完整流程再串一遍,这次是一个全新电脑的视角:
- 打开 PowerShell,先执行 winget --version 确认包管理器可用。
- 执行 winget install Schniz.fnm 安装 fnm。
- 设置环境变量 FNM_NODE_DIST_MIRROR 为镜像地址。
- 编辑 PowerShell 配置文件,添加 fnm env --use-on-cd。
- 重开一个终端,执行 fnm --version 确认可用。
- 执行 fnm install --lts 安装最新长期支持版,等待下载完成。
- 执行 fnm default lts-latest,把默认版本设为刚装的 LTS 版本。
- 执行 node -v 和 npm -v,看到版本号输出,说明整套环境已经通了。
这里有个细节:fnm default 后面可以直接接版本号,也可以接 lts-latest 这样的别名。第一次安装时直接 fnm install --lts 再 fnm default lts-latest 是最省心的组合,不用自己去查当前 LTS 的版本号是多少。查版本号这种事看起来简单,但实际遇到过太多次记错版本号导致的失败,用别名反而一步到位。
4.2 低版本切换到高版本:一个实际项目的完整操作
有一天你的项目报错,提示当前 Node 版本不支持某个特性,要求至少 18 以上,但系统里装的是 16,这时候的操作流程是:
powershell复制# 先看看本地装了哪些版本
fnm ls
# 查看远端 18 版本有哪些可用,过滤一下避免刷屏
fnm list-remote | Select-String "^v18"
# 安装 18 的最新版本,比如列出来的是 v18.20.4
fnm install 18.20.4
# 切换到该版本
fnm use 18.20.4
# 确认生效
node -v
此时如果你再想回到旧版本继续维护老项目,执行 fnm use 16.x.x 即可,两个版本并存互不影响。npm 全局包其实也是按版本隔离的,这也是用版本管理工具的一个重要好处:不同版本环境下装的全局工具不会互相污染。这一点对前端开发尤其重要,比如某些全局脚手架工具在高版本 Node 下安装会报错,在低版本下又引出一堆兼容问题,用 fnm 各自版本配上各自的全局包,从此再也不用为了一个命令行工具反复折腾环境。
4.3 与 VS Code 等 IDE 协作的注意点
很多人在终端里切换了 Node 版本,但打开 VS Code 跑项目时发现还是旧版本。原因在于 VS Code 的集成终端如果是启动后再切换的,它加载的环境变量可能来自 VS Code 启动时的快照。解决方式很简单:在 VS Code 里执行 fnm use 20.11.1 切换版本,或者干脆把 VS Code 完全关闭重开,让集成终端重新加载 shell 配置。
另外,如果你用的是 VS Code 的 TypeScript 语言服务,它有自己的独立进程,切换 Node 版本后建议用 "TypeScript: Restart TS Server" 命令重启一下语言服务,避免编辑器里显示的诊断信息和实际运行环境不一致。类似的道理也适用于其他 IDE,核心原则就是:环境变量是进程级的概念,切换版本后一定要让目标进程重新加载配置,否则看到的信息很可能是假象。
5. 常见问题与排查技巧实录
5.1 提示"fnm 不是内部或外部命令"
这个问题主要出现在刚安装完的一段时间内。原因基本是 PATH 没生效,或者 shell 集成没配置。先重开一个终端再试一次;如果还不行,确认 PATH 里是否包含 fnm 的目录。还有一种隐蔽情况:有些软件在安装过程中往 PATH 里追加了用户变量,但当时已经打开的终端不会自动感知,必须新开窗口。如果你是在旧窗口里反复尝试,肯定会一直失败。
5.2 安装报错"not yet released or is not available"
这其实是网上一搜一大堆的一个错误。比如你执行 fnm install 24.19.0,提示 not yet released or is not available,多半是因为这个版本号还不存在,或者远端索引里还没同步。常见原因有三种:
- 版本号记错了:比如实际发布的是 24.18.2,你手滑打了 24.19.0。
- 镜像源的索引没更新:设置了 FNM_NODE_DIST_MIRROR 后,镜像同步官方版本列表有延迟,全新版本可能还没出现在镜像上。
- 版本号格式不合法:fnm 需要精确的三段式版本号,只写 20 或 20.11 都不行,会被当成非法版本。
排查时先执行 fnm list-remote 过滤一下看看目标版本是否在列表里,如果列表里有但安装仍然失败,再检查镜像源配置。实际操作中,绝大多数情况都是版本号输入错误,尤其是一些刚发布没几天的新版本,版本号记混的概率特别高。
5.3 切换版本不生效,node -v 还是旧版本
这个问题多数情况下是 shell 集成配置不正确导致的。比如你在 PowerShell 配置里漏写了 --use-on-cd,或者 Git Bash 里 eval 语句写错了 shell 类型参数。还有一种可能是系统里有两个 fnm 初始化脚本互相冲突,比如 scoop 自动生成的初始化代码和配置文件里的手动初始化重复执行,导致最后一个执行的环境覆盖了前一个。
排查思路:先执行 fnm env,逐行看输出的环境变量里 PATH 指向的是不是 fnm 的符号链接目录;再执行 Get-Command node(PowerShell)或 which node(Git Bash),看 node 命令实际指向哪里。如果指向的是 C:\Program Files\nodejs 这类目录,说明 fnm 的 PATH 顺序排在系统 Node 后面,把 fnm 的环境初始化代码移到配置文件最前面即可。这一步其实和 Windows 上很多"命令覆盖"类问题的排查逻辑是相通的。
5.4 卸载 fnm 与清理残留
如果以后不想用了,卸载流程也不复杂。先卸载通过包管理器安装的程序,再手动删除 FNM_DIR 目录(默认 %AppData%\fnm 或自定义目录),清理 PATH 里相关条目,最后把 shell 配置文件中添加的那行初始化代码删掉。特别提醒:如果只是删除程序而没有清理 shell 配置,每次打开终端都会报错提示找不到 fnm 命令,虽然不影响使用,但很烦人。我见过不少同事就因为这个,把终端初始化脚本搞得一团糟,最后干脆重装系统。
最后再分享一个我自己的习惯:在日常项目中,我一般不手动执行 fnm use,而是完全依赖 .node-version 文件自动切换,让每个项目"自己声明"需要的版本。刚开始可能觉得多一步创建文件很麻烦,但长期下来,这套习惯能让你在多项目切换时几乎感觉不到版本管理的存在,这才是工具该有的样子。如果你手头有老项目维护、新项目开发并行的情况,强烈建议把 fnm 配上,配合 .node-version 一起用,体验会好很多。
