Node.jsのバージョン管理では、nvm・Volta・miseなどのツールが知られています。中でも有名なのはnvmですが、手動で切り替えしないといけないのと、今使っているプロジェクトのバージョンが記載がないとどれでやるべきか戸惑ってします。誤ったバージョンでやると、互換性などのエラーで開発ができなくなる問題がありました。
そこでpackage.jsonにバージョン記載して、自動で切り替えてやってくれるVoltaを使っていました。バージョンを気にせず開発ができ、複数プロジェクトを同時にやるときは、いちいち手動でバージョン切り替えしなくてもいいので非常に快適でした。
しかしそのVoltaがサポート終了(アーカイブ状態)になったため、公式がmiseを推奨していたため、その移行した作業をご紹介します。
Voltaで困っていたこと
このプロジェクトはもともとVoltaでNode.jsバージョンを固定していた。package.jsonに以下のようなvoltaフィールドを持たせる方式だ。
"volta": {
"node": "22.13.1"
} Volta is unmaintained. Everything that works today should continue to do so for the foreseeable future, so if it is working for you, there is no particular urgency to migrate to another tool, but we will not be able to address breakages from new OS releases or other changes in the ecosystem, so you should put it on your maintenance roadmap at some point. We recommend migrating to mise. See issue #2080. ローカル開発では特に大きな不満がなく、むしろ長年使った行きたいと思っていました。しかしサポート終了していたことを知り、公式を見ると上記の記載がありました。しばらくは動作しそうだが、サポートがされない以上、続けて使うのは不安のため、代替えツールを検討していました。
miseを選んだ理由
Voltaの代替としてmiseを選んだ理由は次の3つ。
- 多言語対応のバージョンマネージャとして広く使われており、開発が活発(Node.js以外にRuby・Python・Goなども同じ仕組みで管理できる)
- 設定ファイルがシンプルなTOML1つで、
mise.tomlにnode = "x.y.z"と書くだけで完結する - シェルへのフック(
mise activate)だけでディレクトリ移動時に自動でバージョン切り替えが効く、Voltaと近い体験をそのまま踏襲できる
「Node バージョンの正をどこか1箇所に一本化する」という方針とも相性がよく、package.jsonからvoltaフィールドを完全に削除してmise.tomlだけに寄せることができた。
またVolta公式がmise推奨していたため、安全に使えると考えました。
移行手順
実際に行った手順は以下の通り。
- Homebrewでmise本体をインストール
brew install mise -
mise use node@22.13.1を実行し、Node 22.13.1をmise管理下にインストール。リポジトリ直下にmise.tomlが自動生成される。[tools] node = "22.13.1"このバージョンは適宜必要なものに変えておいてください。
-
package.jsonからvoltaフィールドを削除"optionalDependencies": { "@rollup/rollup-linux-x64-gnu": "^4.60.1" - }, - "volta": { - "node": "22.13.1" - } + } } - Volta本体をアンインストール。Homebrewで入れていた場合はbrew uninstall voltaで削除し、~/.voltaディレクトリも念のため削除しておく。公式インストーラで入れていた場合は~/.voltaディレクトリを削除し、シェル設定ファイル(~/.zshrcなど)からVOLTA_HOME・PATHへの追記行を手動で削除する。
brew uninstall volta rm -rf ~/.volta -
~/.zshrcにeval "$(mise activate zsh)"を追加し、リポジトリのディレクトリに入ると自動でmise管理下のNodeに切り替わることを確認echo 'eval "$(mise activate zsh)"' >> ~/.zshrc source ~/.zshrc cd /path/to/repo node -v # mise.toml記載のバージョンになっていればOK - 新規シェルで
node -v・npm -vがmise管理下のバージョンを返すことを確認して完了
mise.tomlはgitignore対象にせず、通常のコードと同じようにリポジトリにコミットする。チーム(または将来の自分)がclone直後から同じNodeバージョンを再現できるようにするためだ。
Cloudflare Pagesでの注意点
このブログはCloudflare Pagesを使っているが、Cloudflare Pagesのビルド環境はmise(もVoltaも)を一切認識しない。ビルドに使われるNode.jsバージョンは、ダッシュボードのNODE_VERSION環境変数でしか指定できず、ローカルのmise.tomlを更新してもCloudflare側の値は自動では追従しない。この構造上、mise.tomlとNODE_VERSIONの二重管理は避けられないが、両者のズレを検知できる仕組みを用意した。
具体的には、両者がズレていないかをビルド時にチェックするスクリプトを用意し、食い違いを検知したらビルドを失敗させることで、ローカルでは動くのに本番ビルドだけ古いNodeで動いて失敗する、といった事故を防いでいる。
mise移行にあたって、このチェックスクリプトの参照元もpackage.jsonのvolta.nodeからmise.tomlのnode値に書き換えた。
const miseToml = fs.readFileSync(
path.join(__dirname, '..', 'mise.toml'),
'utf8'
)
const match = miseToml.match(/^\s*node\s*=\s*"([^"]+)"/m)
const expected = match && match[1]
const actual = process.env.NODE_VERSION mise.tomlはnode = "x.y.z"の1行だけを読めればよいシンプルな構成だったため、TOMLパーサーを新たに依存追加するのではなく、正規表現で値を抜き出す実装にとどめた。オーバーエンジニアリングを避け、必要最小限の変更にしている。
スクリプトの中身自体はシンプルで、ローカルビルド(CF_PAGES環境変数が無い)では即座にexit(0)でスキップし、Cloudflare Pages上のビルド(CF_PAGESあり)でのみNODE_VERSIONとmise.tomlの値を比較、不一致ならexit(1)でビルドを失敗させる。
if (!process.env.CF_PAGES) {
process.exit(0)
}
// ...
if (expected && actual !== expected) {
console.error(
`\nNODE_VERSION mismatch: Cloudflare Pages dashboard has "${actual}" but ` +
`mise.toml's node version is "${expected}".\n` +
`Update the NODE_VERSION environment variable in the Cloudflare Pages ` +
`project settings to "${expected}" and redeploy.\n`
)
process.exit(1)
} これはpackage.jsonのprebuildスクリプトとして登録してあるため、npm run buildを叩けば自動的に先に走る。Cloudflare Pages側のビルドコマンドもnpm run buildなので、意識せずとも不一致検知の安全装置が効く仕組みになっている。
CI(GitHub Actions)側はjdx/mise-actionを使うことで、mise.toml通りのNodeバージョンをそのままCIにも反映できる。
- uses: jdx/mise-action@v3 これにより、ローカル・CI・本番ビルドの3箇所のうち、ローカルとCIはmise.tomlを直接参照して自動的に揃い、Cloudflare Pagesだけが構造上どうしても手動同期になる、という状態を明確にできた。「食い違いをゼロにする」のではなく「食い違ったら検知してビルドを止める」という設計にしたのは、Cloudflare Pages側にmise対応が無い以上、完全自動化はできないという制約を受け入れた上での現実的な落とし所だ。
バージョンを上げるとき(例: Node 22.13.1 → 22.22.0への更新)はmise use node@22.22.0一発でmise.tomlが書き換わる。
[tools]
-node = "22.13.1"
+node = "22.22.0" その後、Cloudflare PagesダッシュボードのNODE_VERSION環境変数(Production・Preview両方の環境変数欄)を同じ値に手動で更新するのを忘れないこと。忘れると次のビルドがprebuildの時点で失敗する(これは意図した挙動で、気づかないまま古いNodeで本番が動き続けるよりはよい)。
まとめ
- VoltaはNode.jsバージョン管理として不満はなかったが、サポート終了を理由にmiseへ移行した
- 移行の中身は、
package.jsonのvoltaフィールドを削除してmise.toml([tools]/node = "x.y.z")に一本化しただけで、設定自体はシンプル - Node バージョンを参照する自前スクリプトがある場合は、その読み込み元も忘れず書き換える必要がある(今回は
scripts/check-node-version.cjs) - Cloudflare Pagesのビルド環境はmiseを認識しないため、
NODE_VERSION環境変数は引き続き手動管理が必要。ここはビルド時の不一致検知スクリプトで事故を防ぐようにしている - CIは
jdx/mise-actionを使えばmise.tomlをそのまま読んでくれるため、ローカル・CIの2箇所は自動的に同期が取れる
「Voltaからmiseへの移行」自体は設定ファイルを差し替えるだけの小さな作業だが、Cloudflare PagesのようにNode管理ツールを認識しないビルド環境が絡むプロジェクトでは、移行のついでにバージョン不一致の検知の仕組みまで見直しておくと安心できる。
