Node.jsを使った開発では、プロジェクトごとに異なるNode.jsバージョンを要求されることがあります。
例えば、次のような状況です。
- 既存システムはNode.js 18
- 新規プロジェクトはNode.js 22
- 保守案件では古いNode.jsが必要
- ローカル環境とCI環境のNode.jsバージョンが一致しない
- メンバーごとにNode.jsのバージョンが異なり、不具合の再現条件が揃わない
このような問題を解決するために利用するのが、Node.jsのバージョン管理ツールです。
本記事では、Rustで実装された高速なNode.jsバージョン管理ツール fnm(Fast Node Manager) を、実際の開発現場でどのように導入・運用するかを解説します。
fnmとは
fnmは、複数のNode.jsバージョンをインストールし、プロジェクトごとに切り替えるためのツールです。
正式名称は Fast Node Manager です。
主な特徴は次のとおりです。
- Rust製で起動が高速
- macOS、Linux、Windowsに対応
- 単一バイナリで動作
-
.node-versionに対応 -
.nvmrcに対応 - ディレクトリ移動時の自動切り替えに対応
- Bash、Zsh、Fish、PowerShellなどに対応
公式リポジトリでも、クロスプラットフォーム対応、単一ファイル、起動速度、.node-versionと.nvmrcへの対応が主要機能として挙げられています。
開発現場でfnmを使うメリット
1. プロジェクトごとにNode.jsを切り替えられる
プロジェクトAではNode.js 20、プロジェクトBではNode.js 22というように、複数バージョンを共存させられます。
projects/
├── legacy-api/ Node.js 18
├── admin-frontend/ Node.js 20
└── new-platform/ Node.js 22
システム全体のNode.jsを入れ替える必要がないため、既存案件への影響を抑えられます。
2. 開発メンバーの環境を統一できる
プロジェクトルートにバージョンファイルを置いてGit管理することで、チーム全員が同じNode.jsバージョンを使用できます。
.node-version
22.14.0
3. ディレクトリ移動だけでバージョンを切り替えられる
--use-on-cdを有効にすると、プロジェクトディレクトリへ移動した際に、.node-versionまたは.nvmrcを検出してNode.jsバージョンを自動的に切り替えられます。
cd ~/projects/legacy-api
node --version
# v18.x.x
cd ~/projects/new-platform
node --version
# v22.x.x
手動で毎回fnm useを実行する必要がなくなります。
4. Windowsを含む複数OSで運用しやすい
fnmはmacOS、Linux、Windowsに対応しています。
フロントエンドチームはmacOS、業務システムチームはWindows、サーバーやCIはLinuxという構成でも、同じ仕組みを採用できます。
fnmのインストール
macOS
Homebrewを利用します。
brew install fnm
公式リポジトリでは、macOSおよびLinux向けのHomebrewインストール方法が案内されています。
インストールを確認します。
fnm --version
Linux
インストールスクリプトを利用します。
curl -fsSL https://fnm.vercel.app/install | bash
インストールスクリプトを利用する場合、curlとunzipが必要です。
Ubuntuでは、事前に次のようにインストールできます。
sudo apt update
sudo apt install -y curl unzip
その後、fnmをインストールします。
curl -fsSL https://fnm.vercel.app/install | bash
Windows
wingetを使う場合
winget install Schniz.fnm
Scoopを使う場合
scoop install fnm
Chocolateyを使う場合
choco install fnm
これらのWindows向けインストール方法は、公式リポジトリでも案内されています。
インストール後は、ターミナルを開き直して確認します。
fnm --version
シェルの初期設定
fnmは、fnm envの出力をシェルで評価することで、Node.jsへのパスや環境変数を設定します。公式ドキュメントでは、実行時のシェル推測を避けるため、--shellでシェルを明示する方法が推奨されています。
Zsh
macOSの標準シェルであるZshを利用している場合は、~/.zshrcへ追加します。
eval "$(fnm env --use-on-cd --shell zsh)"
設定を読み込みます。
source ~/.zshrc
Bash
~/.bashrcへ追加します。
eval "$(fnm env --use-on-cd --shell bash)"
設定を読み込みます。
source ~/.bashrc
Fish
~/.config/fish/conf.d/fnm.fishを作成し、次を記述します。
fnm env --use-on-cd --shell fish | source
PowerShell
最初にPowerShellプロファイルを作成します。
if (-not (Test-Path $PROFILE)) {
New-Item $PROFILE -Force
}
プロファイルを開きます。
Invoke-Item $PROFILE
次の行を追加します。
fnm env --use-on-cd --shell powershell | Out-String | Invoke-Expression
PowerShellのプロファイル設定方法と--use-on-cdを含む初期化コマンドは、公式リポジトリに掲載されています。
設定後、PowerShellを再起動します。
Node.jsをインストールする
LTS版をインストールする
fnm install --lts
インストール後、そのバージョンを利用します。
fnm use --lts
バージョンを確認します。
node --version
npm --version
バージョンを指定してインストールする
fnm install 22.14.0
インストールしたバージョンへ切り替えます。
fnm use 22.14.0
確認します。
node --version
v22.14.0
メジャーバージョンだけを指定することもできます。
fnm install 22
fnm use 22
ただし、開発現場では再現性を高めるため、プロジェクトのバージョンファイルにはできるだけパッチバージョンまで記述することをおすすめします。
基本コマンド
インストール済みバージョンを確認する
fnm list
環境によっては、次の短縮形も利用できます。
fnm ls
現在のバージョンを確認する
fnm current
Node.js自身のコマンドでも確認できます。
node --version
バージョンを切り替える
fnm use 20
fnm use 22.14.0
バージョンをアンインストールする
fnm uninstall 18.20.5
削除前に、利用中のプロジェクトがないか確認しておきましょう。
デフォルトバージョンを設定する
プロジェクト外で使用する標準バージョンを設定します。
fnm default 22.14.0
新しいターミナルを起動し、確認します。
node --version
プロジェクトごとにNode.jsを固定する
fnmを開発現場へ導入するうえで、最も重要なのがバージョンファイルの運用です。
.node-versionを利用する
プロジェクトルートで次を実行します。
echo "22.14.0" > .node-version
現在使用しているNode.jsバージョンを書き出す場合は、次の方法もあります。
node --version > .node-version
公式リポジトリでも、node --versionの出力を.node-versionへ保存する例が案内されています。
ファイルの内容は次のようになります。
v22.14.0
vを付けずに記述しても運用できます。
22.14.0
.nvmrcを利用する
既にnvmを利用しているプロジェクトでは、.nvmrcをそのまま利用できます。
echo "22.14.0" > .nvmrc
fnmは.node-versionと.nvmrcの両方に対応しています。
そのため、nvmからfnmへ移行する場合でも、既存リポジトリの変更を最小限にできます。
バージョンファイルはGit管理する
.node-versionまたは.nvmrcは、原則としてGitへコミットします。
git add .node-version
git commit -m "chore: pin Node.js version"
これにより、リポジトリをクローンしたメンバーが同じNode.jsバージョンを利用できます。
バージョンファイルを個人設定として.gitignoreへ追加してしまうと、チーム内でバージョンを統一できません。
開発現場でおすすめの運用方法
1. パッチバージョンまで固定する
次のようにメジャーバージョンだけを記述すると、インストールするタイミングによって利用されるバージョンが変化する可能性があります。
22
再現性を重視する場合は、パッチバージョンまで固定します。
22.14.0
これにより、次の環境を揃えやすくなります。
- 開発者のローカル環境
- Dockerビルド
- CI/CD
- ステージング環境
- 本番環境
2. package.jsonにもNode.js要件を記述する
.node-versionだけでなく、package.jsonのenginesにもNode.jsバージョンを記述します。
{
"name": "example-project",
"private": true,
"engines": {
"node": "22.14.x"
}
}
役割は少し異なります。
| ファイル | 主な役割 |
|---|---|
.node-version |
fnmが実際に使用するNode.jsバージョン |
.nvmrc |
nvmやfnmなどで使用するNode.jsバージョン |
package.jsonのengines
|
アプリケーションが対応するNode.jsの条件 |
複数の仕組みを組み合わせることで、誤ったバージョンの利用を検知しやすくなります。
3. READMEに初期構築手順を書く
新しく参加したメンバー向けに、READMEへ手順を記載します。
## 開発環境
### 必要なツール
- fnm
- Node.js
- pnpm
### セットアップ
```bash
fnm install
fnm use
corepack enable
pnpm install
バージョンファイルが存在するディレクトリでは、引数なしの`fnm install`や`fnm use`によって、対象バージョンを利用できる構成にしておくとスムーズです。
---
## 4. `--use-on-cd`を標準設定にする
チームのセットアップ手順では、次の設定を推奨します。
```bash
eval "$(fnm env --use-on-cd --shell zsh)"
ディレクトリ移動時の自動切り替えを有効にすることで、別プロジェクトのNode.jsバージョンを使ったまま作業するミスを減らせます。
5. package managerも固定する
Node.jsのバージョンだけでなく、npm、pnpm、Yarnなどのバージョン差もビルド結果へ影響することがあります。
package.jsonのpackageManagerを利用します。
{
"packageManager": "pnpm@10.6.5"
}
Corepackを有効化します。
corepack enable
その後、指定されたpackage managerを利用します。
pnpm install
これにより、次の2つを固定できます。
Node.js .node-version
package manager package.jsonのpackageManager
新規プロジェクトでの導入例
React/Next.jsプロジェクト
mkdir example-frontend
cd example-frontend
echo "22.14.0" > .node-version
fnm install
fnm use
corepack enable
npx create-next-app@latest .
バージョンを確認します。
node --version
npm --version
.node-versionをコミットします。
git add .node-version
git commit -m "chore: add Node.js version"
LaravelとNode.jsを併用するプロジェクト
Laravelプロジェクトでも、ViteやTailwind CSSを利用する場合はNode.jsが必要です。
cd example-laravel-project
echo "22.14.0" > .node-version
fnm install
fnm use
npm ci
npm run build
PHPのバージョン管理とNode.jsのバージョン管理を分離して考えると、環境構築が整理しやすくなります。
PHP mise / asdf / Docker
Node.js fnm
既存プロジェクトへ導入する
既存プロジェクトでは、現在正常に動作しているNode.jsバージョンを確認します。
node --version
v20.18.3
そのバージョンを.node-versionへ保存します。
node --version > .node-version
または手動で作成します。
echo "20.18.3" > .node-version
package.jsonにも要件を追加します。
{
"engines": {
"node": "20.18.x"
}
}
その後、CI、Docker、本番環境のNode.jsバージョンも揃えます。
nvmからfnmへ移行する
fnmは.nvmrcを読み込めるため、nvmから比較的移行しやすいツールです。
1. fnmをインストールする
macOSの場合は次のとおりです。
brew install fnm
2. シェル設定を追加する
eval "$(fnm env --use-on-cd --shell zsh)"
3. .nvmrcのバージョンをインストールする
プロジェクトルートで実行します。
fnm install
fnm use
4. 動作を確認する
node --version
npm --version
5. nvmの設定を削除する
動作確認後、.zshrcや.bashrcに残っているnvm関連の設定を削除します。
例:
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
nvmとfnmを同時に初期化すると、PATHの順序によって意図しないNode.jsが利用される可能性があります。
移行期間を除き、バージョン管理ツールは1つに統一する方が安全です。
Dockerとfnmの役割分担
Dockerを使っている場合、「fnmは不要ではないか」と考えることがあります。
実際には、fnmとDockerは役割が異なります。
| ツール | 主な役割 |
|---|---|
| fnm | ローカル開発環境のNode.js切り替え |
| Docker | コンテナ内の実行環境を固定 |
.node-version |
ローカルで利用するバージョンを宣言 |
Dockerfile |
コンテナで利用するバージョンを宣言 |
例えば、.node-versionが次の内容だとします。
22.14.0
Dockerfileも同じバージョンへ揃えます。
FROM node:22.14.0-bookworm-slim
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build
CMD ["npm", "start"]
Node.jsバージョンを更新する場合は、次の両方を変更します。
.node-version
Dockerfile
RenovateやDependabotなどでDockerイメージを更新している場合は、バージョンファイルとの不一致をレビュー時に確認します。
CI/CDでの利用方針
CI/CD環境では、必ずしもfnm自体を利用する必要はありません。
GitHub Actionsでは、通常はactions/setup-nodeを利用します。
重要なのは、ローカル環境とCI環境で同じNode.jsバージョンを参照することです。
GitHub Actionsの例
.node-versionを読み込んでsetup-nodeへ渡します。
name: CI
on:
push:
pull_request:
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Read Node.js version
id: node-version
shell: bash
run: |
echo "version=$(tr -d 'v[:space:]' < .node-version)" >> "$GITHUB_OUTPUT"
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ steps.node-version.outputs.version }}
cache: npm
- name: Install dependencies
run: npm ci
- name: Lint
run: npm run lint
- name: Test
run: npm test
- name: Build
run: npm run build
この構成では、Node.jsのバージョンを.node-versionへ集約できます。
CI上でfnmを直接利用する例
開発環境と同じ挙動を検証したい場合は、CIへfnmをインストールする方法もあります。
name: CI with fnm
on:
push:
pull_request:
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install fnm
shell: bash
run: |
curl -fsSL https://fnm.vercel.app/install | bash -s -- --skip-shell
echo "$HOME/.local/share/fnm" >> "$GITHUB_PATH"
- name: Install Node.js
shell: bash
run: |
eval "$(fnm env --shell bash)"
fnm install
fnm use
node --version
npm --version
- name: Install dependencies
shell: bash
run: |
eval "$(fnm env --shell bash)"
fnm use
npm ci
- name: Test
shell: bash
run: |
eval "$(fnm env --shell bash)"
fnm use
npm test
ただし、GitHub Actionsではactions/setup-nodeの方がキャッシュ設定などを簡潔に記述できます。
そのため、多くのプロジェクトでは次の分担が現実的です。
ローカル環境:fnm
GitHub Actions:actions/setup-node
バージョン定義:.node-version
Makefileやスクリプトへ組み込む
初期構築手順を自動化する場合、Makefileへ組み込めます。
.PHONY: setup install test build check-node
setup:
fnm install
fnm use
corepack enable
npm ci
install:
fnm use
npm ci
test:
fnm use
npm test
build:
fnm use
npm run build
check-node:
@echo "Expected: $$(cat .node-version)"
@echo "Actual: $$(node --version)"
ただし、Makefileは各行が別のシェルで実行される場合があります。
fnmの初期化が必要な環境では、次のように1つのシェル内で実行します。
SHELL := /bin/bash
.PHONY: setup
setup:
eval "$$(fnm env --shell bash)" && \
fnm install && \
fnm use && \
corepack enable && \
npm ci
開発コンテナやリモート環境で使う
WSL2
WSL2では、Windows側ではなくWSL側へfnmをインストールします。
curl -fsSL https://fnm.vercel.app/install | bash
~/.bashrcまたは~/.zshrcへ追加します。
eval "$(fnm env --use-on-cd --shell bash)"
Windows側のNode.jsとWSL側のNode.jsは別環境として管理します。
確認には次のコマンドを利用します。
which node
node --version
fnm current
VS Code Dev Containers
Dev ContainerではDockerfileのNode.jsバージョンを固定するため、コンテナ内でfnmを使わない構成も一般的です。
FROM node:22.14.0-bookworm
一方、1つの開発コンテナ内で複数Node.jsバージョンを検証したい場合は、fnmを追加できます。
RUN apt-get update \
&& apt-get install -y curl unzip \
&& curl -fsSL https://fnm.vercel.app/install | bash
ただし、通常のアプリケーション開発では、コンテナ自体のNode.jsバージョンを固定する方が構成は単純です。
よくあるトラブルと対処法
fnm: command not found
原因
- fnmのインストール先がPATHに入っていない
- シェル設定を読み込んでいない
- インストール後にターミナルを再起動していない
対処
source ~/.zshrc
または次を確認します。
command -v fnm
echo "$PATH"
node: command not found
fnmはインストールされていても、Node.jsが未インストール、またはfnm envが評価されていない可能性があります。
fnm install --lts
fnm use --lts
現在のシェルだけで初期化する場合は、次を実行します。
eval "$(fnm env --shell zsh)"
Bashの場合は次のとおりです。
eval "$(fnm env --shell bash)"
ディレクトリを移動してもバージョンが切り替わらない
--use-on-cdが設定されているか確認します。
Zshの場合:
eval "$(fnm env --use-on-cd --shell zsh)"
Bashの場合:
eval "$(fnm env --use-on-cd --shell bash)"
また、プロジェクトルートにバージョンファイルがあるか確認します。
ls -la .node-version .nvmrc
fnm useでバージョンが見つからない
対象バージョンが未インストールです。
fnm install
fnm use
または明示的に指定します。
fnm install 22.14.0
fnm use 22.14.0
which nodeがfnm配下を指していない
macOS/Linuxでは次を確認します。
which node
fnm管理下のNode.jsではなく、Homebrew、apt、nvm、asdfなどのNode.jsが先に見つかっている可能性があります。
シェル設定を確認します。
grep -n "fnm\|nvm\|asdf\|node" ~/.zshrc
Node.jsの管理ツールが複数初期化されている場合は、利用するツールを整理します。
VS Codeのターミナルだけバージョンが異なる
VS Codeをシェル設定変更前から起動している場合、古い環境変数を保持している可能性があります。
次を試します。
- VS Codeのターミナルを閉じる
- VS Codeを終了する
- VS Codeを再起動する
- 新しいターミナルで確認する
node --version
fnm current
which node
npmのグローバルパッケージが消えた
Node.jsの各バージョンは、基本的に別のグローバルパッケージ領域を持ちます。
例えば、Node.js 20でインストールしたCLIが、Node.js 22では見つからない場合があります。
npm install -g typescript
開発現場では、CLIをグローバルインストールするよりも、プロジェクトのdevDependenciesへ追加する方が再現性を高められます。
npm install --save-dev typescript
実行時はnpxやpackage scriptsを利用します。
npx tsc --version
{
"scripts": {
"typecheck": "tsc --noEmit"
}
}
セキュリティ面での注意
インストールスクリプトをそのままパイプしない選択肢
次のインストール方法は簡単ですが、取得したスクリプトを直接実行します。
curl -fsSL https://fnm.vercel.app/install | bash
セキュリティポリシーが厳しい組織では、いったんファイルへ保存して内容を確認します。
curl -fsSL https://fnm.vercel.app/install -o install-fnm.sh
less install-fnm.sh
bash install-fnm.sh
または、Homebrew、winget、Scoopなど、組織で許可されたパッケージマネージャーを利用します。
CIではバージョンやハッシュを固定する
CI上で常に最新のインストールスクリプトを取得すると、将来的な変更の影響を受ける可能性があります。
厳密な環境では、次の方法を検討します。
- リリースバージョンを固定する
- バイナリのチェックサムを検証する
- 社内アーティファクトリポジトリへ配置する
- Dockerイメージへあらかじめ組み込む
- CIでは公式のNode.jsセットアップActionを利用する
チーム向けの推奨構成
筆者が開発現場で採用する場合は、次の構成にします。
.node-version
22.14.0
package.json
{
"private": true,
"engines": {
"node": "22.14.x"
},
"packageManager": "pnpm@10.6.5",
"scripts": {
"dev": "vite",
"lint": "eslint .",
"typecheck": "tsc --noEmit",
"test": "vitest run",
"build": "vite build"
}
}
.zshrc
eval "$(fnm env --use-on-cd --shell zsh)"
セットアップコマンド
fnm install
fnm use
corepack enable
pnpm install
GitHub Actions
- name: Read Node.js version
id: node-version
shell: bash
run: |
echo "version=$(tr -d 'v[:space:]' < .node-version)" >> "$GITHUB_OUTPUT"
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ steps.node-version.outputs.version }}
cache: pnpm
この構成では、Node.jsバージョンの基準を.node-versionへ集約できます。
バージョンアップの進め方
Node.jsのバージョンアップは、単に.node-versionを書き換えるだけでなく、次の順序で進めます。
1. 新しいバージョンをインストールする
fnm install 22.15.0
fnm use 22.15.0
2. バージョンファイルを更新する
echo "22.15.0" > .node-version
3. 依存関係を再インストールする
npmの場合:
rm -rf node_modules
npm ci
pnpmの場合:
rm -rf node_modules
pnpm install --frozen-lockfile
4. 静的解析とテストを実行する
npm run lint
npm run typecheck
npm test
npm run build
5. Dockerfileを更新する
FROM node:22.15.0-bookworm-slim
6. CIで検証する
プルリクエスト上で、次を確認します。
- lint
- typecheck
- unit test
- integration test
- build
- Docker build
- E2E test
Node.js更新だけのプルリクエストとして分離すると、不具合発生時に原因を追跡しやすくなります。
fnmを採用するときの判断基準
fnmは次のようなチームに向いています。
- Node.jsプロジェクトを複数扱う
- nvmのシェル起動速度が気になる
- Windows、macOS、Linuxが混在している
-
.nvmrcを維持したまま移行したい - ディレクトリ移動時に自動切り替えしたい
- Node.js専用のシンプルな管理ツールを使いたい
一方で、Node.js以外にもPython、Ruby、Go、Java、Terraformなどをまとめて管理したい場合は、miseやasdfなどのマルチランタイム管理ツールも候補になります。
ツールの優劣だけでなく、チームが管理したい対象や、既存環境との整合性を基準に選ぶことが重要です。
まとめ
fnmを利用すると、Node.jsのバージョン切り替えを高速かつシンプルに管理できます。
特に開発現場では、fnmをインストールするだけでなく、次の運用までセットで導入することが重要です。
-
.node-versionまたは.nvmrcをGit管理する - パッチバージョンまで固定する
-
--use-on-cdで自動切り替えを有効にする -
package.jsonのenginesも設定する - package managerのバージョンも固定する
- DockerfileとCIのNode.jsバージョンを揃える
- グローバルCLIへの依存を減らす
- バージョン更新を独立したプルリクエストにする
fnmの導入目的は、単にNode.jsを切り替えることではありません。
ローカル、CI、Docker、本番環境でNode.jsバージョンを揃え、開発環境の再現性を高めることが、本質的な目的です。
Node.jsのバージョン差によるトラブルが多いチームでは、fnmとバージョンファイルの運用をセットで導入してみてください。
参考資料
- Schniz/fnm GitHubリポジトリ
- fnm公式インストールガイド