はじめに
チームプロジェクトで「自分のPCでは動くんだけど?」という言葉を聞いたことはありませんか?この問題の最も一般的な原因の一つが、Node.jsバージョンの不一致です。
開発者ごとにローカルにインストールされたNode.jsバージョンが異なると、依存関係のインストール結果が変わったり、ビルドが失敗したり、ランタイムで微妙なバグが発生したりする可能性があります。特にモノレポ環境では複数のアプリが同じNode.jsランタイムを共有するため、バージョンの統一がさらに重要になります。
この記事では、.nvmrc、package.jsonのenginesフィールド、そしてYarn Berryカスタムプラグインを組み合わせて、ローカル開発環境とCI環境の両方でNode.jsバージョンを強制する方法を紹介します。
なぜNode.jsバージョンを固定すべきなのか?
1. セキュリティ脆弱性への対応
Node.jsは定期的にセキュリティパッチをリリースしています。特定のバージョンで発見された脆弱性がチーム内の一部の開発者の環境にのみパッチされていると、開発・ステージング・プロダクション間の動作が異なる可能性があります。
2. バージョンによるビルド成果物の違い
同じソースコードでも、Node.jsバージョンによってビルド成果物が異なることがあります。ネイティブモジュールのコンパイル結果、cryptoモジュールの動作、fs APIの変更などが代表的な例です。
3. 依存関係の互換性
package-lock.jsonやyarn.lockが同じでも、Node.jsバージョンが異なるとオプショナルな依存関係(optional dependencies)やプラットフォーム別パッケージのインストール結果が変わることがあります。
バージョン固定のための3つのレイヤー
一つの方法だけに頼るのではなく、複数のレイヤーを組み合わせることで、より確実にバージョンを強制できます。
レイヤー1:.nvmrc — バージョンの単一の信頼できる情報源(Single Source of Truth)
.nvmrcファイルはプロジェクトルートに配置され、そのプロジェクトで使用するNode.jsバージョンを明示します。
24.13.0
開発者はnvm useコマンドでこのバージョンに切り替えることができます:
$ nvm use
Found '/path/to/project/.nvmrc' with version <24.13.0>
Now using node v24.13.0 (npm v10.x.x)
Tip: シェル設定(
.zshrc、.bashrc)に自動切り替えスクリプトを追加すると、プロジェクトディレクトリに入った時に自動的に正しいNode.jsバージョンに切り替わります。
nvm自動切り替えスクリプトの設定
毎回nvm useを手動で入力するのは面倒で忘れやすいです。シェル設定ファイルに以下のスクリプトを追加すると、cdでディレクトリを移動する際に.nvmrcファイルを検知して自動的にNode.jsバージョンを切り替えてくれます。
Bash (~/.bashrc):
cdnvm() {
command cd "$@" || return $?
nvm_path="$(nvm_find_up .nvmrc | command tr -d '\n')"
# .nvmrcファイルがないディレクトリに移動した場合、デフォルトバージョンに復元
if [[ ! $nvm_path = *[^[:space:]]* ]]; then
declare default_version
default_version="$(nvm version default)"
if [[ $default_version == "N/A" ]]; then
nvm alias default node
default_version=$(nvm version default)
fi
if [[ $(nvm current) != "$default_version" ]]; then
nvm use default
fi
elif [[ -s "${nvm_path}/.nvmrc" && -r "${nvm_path}/.nvmrc" ]]; then
declare nvm_version
nvm_version=$(<"${nvm_path}/.nvmrc")
declare locally_resolved_nvm_version
locally_resolved_nvm_version=$(nvm ls --no-colors "$nvm_version" | command tail -1 | command tr -d '\->*' | command tr -d '[:space:]')
if [[ "$locally_resolved_nvm_version" == "N/A" ]]; then
nvm install "$nvm_version"
elif [[ $(nvm current) != "$locally_resolved_nvm_version" ]]; then
nvm use "$nvm_version"
fi
fi
}
alias cd='cdnvm'
cdnvm "$PWD" || exit
Zsh (~/.zshrc):
autoload -U add-zsh-hook
load-nvmrc() {
local nvmrc_path
nvmrc_path="$(nvm_find_nvmrc)"
if [ -n "$nvmrc_path" ]; then
local nvmrc_node_version
nvmrc_node_version=$(nvm version "$(cat "${nvmrc_path}")")
if [ "$nvmrc_node_version" = "N/A" ]; then
nvm install
elif [ "$nvmrc_node_version" != "$(nvm version)" ]; then
nvm use
fi
elif [ -n "$(PWD=$OLDPWD nvm_find_nvmrc)" ] && [ "$(nvm version)" != "$(nvm version default)" ]; then
echo "Reverting to nvm default version"
nvm use default
fi
}
add-zsh-hook chpwd load-nvmrc
load-nvmrc
上記のスクリプトを設定すると、次のように動作します:
.nvmrcがあるプロジェクトディレクトリに移動すると、自動的にそのバージョンに切り替わります。- そのバージョンがインストールされていない場合、自動的に
nvm installを実行します。 .nvmrcがないディレクトリに移動すると、nvmのデフォルト(default)バージョンに復元されます。
しかし、.nvmrcだけでは強制力がありません。自動切り替えスクリプトを設定していない開発者や、nvm useを実行し忘れた場合は、依然として異なるバージョンのNode.jsで作業することになります。
レイヤー2:package.jsonのenginesフィールド
package.jsonにenginesフィールドを追加して、許可されるNode.jsバージョンを宣言できます。
{
"engines": {
"node": "24.13.0"
},
"engineStrict": true
}
npmを使用する場合、プロジェクトルートの.npmrcファイルにengine-strictオプションを設定すると、npm install時にバージョンが合わない場合エラーが発生します:
# .npmrc
engine-strict=true
この設定がなければ、enginesフィールドは警告(warning)のみを出力してインストールが続行されます。engine-strict=trueを設定してはじめてエラーとして処理され、インストールが中断されます。
参考:
package.jsonのengineStrictフィールドはnpm v3から**非推奨(deprecated)**になっています。代わりに.npmrcファイルでengine-strict=trueを設定するのが正しい方法です。
しかし、Yarn Berry(v2以上)ではenginesフィールドをデフォルトでは検証しません。.npmrcの設定もYarn Berryには適用されません。これがまさに次のレイヤーが必要な理由です。
レイヤー3:Yarn Berryカスタムプラグイン — 本当の強制
Yarn Berryはプラグインシステムを提供しており、validateProjectフックを通じてyarn installなどのコマンド実行前にカスタム検証ロジックを挿入できます。
プラグインコード
.yarn/plugins/plugin-check-node.js:
module.exports = {
name: 'plugin-check-node',
factory: () => ({
hooks: {
validateProject(project) {
const fs = require('fs');
const path = require('path');
// Yarn Berryのproject.cwdはPortablePath(POSIX形式)を返します。
// Windowsではドライブレターの前の"/"を削除する必要があります。
let cwd = project.cwd;
if (process.platform === 'win32' && /^\/[A-Za-z]:/.test(cwd)) {
cwd = cwd.slice(1);
}
const nvmrcPath = path.join(cwd, '.nvmrc');
let required;
try {
required = fs.readFileSync(nvmrcPath, 'utf8').trim();
} catch (error) {
if (error && error.code === 'ENOENT') {
throw new Error(
`\n\x1b[31mUnable to determine required Node version.\x1b[0m\n` +
`The .nvmrc file was not found at: ${nvmrcPath}\n\n` +
`Please create a .nvmrc file with the required Node version,\n` +
`or ensure you are running the command in the correct project directory.\n`
);
}
throw new Error(
`\n\x1b[31mUnable to read required Node version from .nvmrc.\x1b[0m\n` +
`Path: ${nvmrcPath}\n` +
`Underlying error: ${error && error.message ? error.message : String(error)}\n`
);
}
const current = process.versions.node;
if (current !== required) {
throw new Error(
`\n\x1b[31mNode version mismatch!\x1b[0m\n` +
`Required: ${required}\n` +
`Current: ${current}\n\n` +
`Please run: nvm use\n`
);
}
},
},
}),
};
プラグインが行うこと
.nvmrcファイルから必要なNode.jsバージョンを読み取ります。- 現在実行中のNode.jsバージョン(
process.versions.node)と比較します。 - バージョンが一致しない場合、エラーを発生させてコマンドの実行を中断します。
- Windows環境でのパス互換性も処理します。
プラグインの登録
.yarnrc.ymlにプラグインを登録します:
nodeLinker: node-modules
plugins:
- .yarn/plugins/plugin-check-node.js
動作結果
正しくないNode.jsバージョンでyarn installを実行すると、次のようなエラーが出力されます:
Node version mismatch!
Required: 24.13.0
Current: 20.11.0
Please run: nvm use
このエラーはyarn installだけでなく、Yarnを通じて実行されるすべてのコマンド(yarn build、yarn devなど)で発生します。つまり、間違ったバージョンでは一切作業ができなくなります。
CI環境でのバージョン統一
ローカル開発環境だけでなく、CI(Continuous Integration)環境でも同一のNode.jsバージョンを使用する必要があります。
GitHub Actionsでは、actions/setup-nodeのnode-version-fileオプションを通じて.nvmrcファイルを直接参照できます。.nvmrcに記録されたバージョン(例:24.13.0)をsetup-nodeアクションが読み取り、該当バージョンのNode.jsを自動的にインストールするため、ワークフローファイルにバージョンをハードコードする必要がありません:
steps:
- name: Setup node
uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
node-version-fileオプションは.nvmrc以外にも.node-version、.tool-versionsなどのファイル形式をサポートしています。重要なのは、node-versionではなくnode-version-fileを使用することで、バージョン番号をワークフローYAMLに直接記述しないという点です。これにより、.nvmrcファイルがローカル開発環境とCI環境の両方の**単一の信頼できる情報源(Single Source of Truth)**の役割を果たします。
- 単一の信頼できる情報源(Single Source of Truth):特定のデータや設定値をただ一つのソースからのみ管理する原則です。複数の場所に同じ値を重複して記述すると更新時に漏れが発生しやすいですが、単一の情報源を設ければ一箇所を修正するだけでそれを参照するすべての場所に自動的に反映されます。ここでは
.nvmrcファイルがNode.jsバージョンの単一の信頼できる情報源の役割を果たしています。
実際のプロジェクトでは、この設定を再利用可能なComposite Actionとして分離し、すべてのワークフローで一貫して使用できます。
# .github/actions/install_dependencies/action.yml
name: 'Install Dependencies'
runs:
using: 'composite'
steps:
- name: Setup node
uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
- name: Enable Yarn
shell: bash
run: corepack enable
- name: Install dependencies
shell: bash
run: yarn install --frozen-lockfile
これにより、Node.jsバージョンを更新する際に.nvmrcファイル一つだけ修正すれば、ローカルとCI環境の両方に自動的に反映されます。
GitHub Actionsでのワークフロー設計についてさらに知りたい方は、Jest実行Actionのパフォーマンス改善やモノレポでブランチ別ファイル変更範囲を制限するGitHub Actionsワークフローも参考にしてみてください。
バージョン更新プロセス
Node.jsバージョンを更新する必要がある場合(例:セキュリティパッチ)、以下のファイルを修正する必要があります。
| ファイル | 役割 |
|---|---|
.nvmrc | ローカル開発者 + CIのNode.jsバージョン指定(単一の信頼できる情報源) |
package.jsonのengines | npmユーザーのための追加セーフガード |
核心は**.nvmrcファイル一つだけ修正すればよい**という点です。プラグインとCI設定はすべて.nvmrcを参照するため、バージョン管理が非常にシンプルになります。
全体構造の概要
重要なポイント
.nvmrcを単一の信頼できる情報源に — すべてのツールがこのファイルを参照するように設計します。- Yarn Berryプラグインで強制力を確保 —
nvm useを忘れても、間違ったバージョンでは作業が不可能になります。 - CIでも同じソースを参照 —
node-version-fileオプションで.nvmrcを直接読み取ります。 - Windows互換性を考慮 — プラグインでYarn BerryのPortablePathをネイティブパスに変換します。
- バージョン更新はファイル一つだけ —
.nvmrcの修正一回でローカルとCIの両方が更新されます。
参考資料
- Yarn BerryでNodeバージョンを固定する(GitHub Issue #1177)
- actions/setup-node — node-version-fileオプション
- Node.jsセキュリティリリース(2025年12月)
私のブログが役に立ちましたか?下にコメントを残してください。それは私にとって大きな大きな力になります!
アプリ広報
Dekuが開発したアプリを使ってみてください。Dekuが開発したアプリはFlutterで開発されています。興味がある方はアプリをダウンロードしてアプリを使ってくれると本当に助かります。