package-lock.json は、パッケージを1つ足すだけで数百行の差分が出ることがあります。そのため「自動で作られるファイルだし、差分がうるさいから .gitignore に入れてしまおう」と考える人もいます。
結論から書くと、アプリのリポジトリでは package-lock.json はコミットしたほうがいいと考えています。理由は、これがないと「自分の手元」と「ほかの人の手元・本番のビルド」で、入るパッケージのバージョンが変わりうるからです。この記事では、npm 11.11.0 で実際に試した結果をもとに、その理由と npm install / npm ci の使い分けを整理します。
package.json だけでは、入るバージョンは決まらない
package.json の依存は、たいてい ^ 付きで書かれます(抜粋)。
{
"dependencies": {
"ms": "^2.0.0"
}
}
^2.0.0 は「2.0.0 以上、3.0.0 未満ならどれでもいい」という範囲の指定です。範囲なので、実際にどのバージョンが入るかは、インストールしたタイミングで変わりえます。
それを固定しているのが package-lock.json です。実際に入ったバージョンと、どこから取ってきたかが記録されています(抜粋)。
"node_modules/ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
実際に試した結果
ms を 2.0.0 で入れたあと、package.json を "^2.0.0" に書き換えた状態で比べました。
| 状況 | 実行したコマンド | 入ったバージョン |
|---|---|---|
package-lock.json あり(2.0.0 が記録されている) |
npm install |
2.0.0 |
package-lock.json だけ消した(node_modules は残っている) |
npm install |
2.0.0 |
package-lock.json と node_modules を消した(clone 直後と同じ状態) |
npm install |
2.1.3 |
手元では、ロックファイルを消しても node_modules に入っている版が使われ続けたので、違いに気づきにくいです。ところが node_modules もない状態、つまり別の人が clone した直後と同じ状態では、同じ package.json から 2.1.3 が入りました。
package-lock.json をコミットしていないと、ほかの人が git clone して npm install したときや、Vercel などで本番用に build したときに、範囲内でそのとき最新のバージョンが入ることがあります。手元では動くのに本番の build だけ失敗する、ということが起きたときに、原因の切り分けが難しくなります。
npm ci は「ロックファイルどおりに入れる」コマンド
npm ci は、package-lock.json に書かれたとおりに入れるためのコマンドです。npm install との違いを、実際に試した結果でまとめます。
| 状況 | npm install |
npm ci |
|---|---|---|
| ロックファイルあり・package.json と一致 | ロックどおりに入る | ロックどおりに入る |
| package.json だけ書き換えた(ロックと不一致) | ロックファイルを書き換えて入れる | エラーで止まる |
ロックファイルも node_modules もない |
範囲内の最新を入れて、ロックファイルを作る | エラーで止まる |
package.json に手で "is-number": "^7.0.0" を足し、npm install をせずに npm ci を実行すると、こう言って止まりました。
npm error code EUSAGE
npm error `npm ci` can only install packages when your package.json and package-lock.json or npm-shrinkwrap.json are in sync. Please update your lock file with `npm install` before continuing.
npm error Missing: is-number@7.0.0 from lock file
npm install は「足りないものを足して、ロックファイルも直す」コマンドなので、食い違いをエラーにせずに埋めます。npm ci は「食い違いがあれば止まる」ので、ロックファイルの更新忘れに気づけます。
使い分けの目安
- パッケージを足す・上げるとき →
npm install <名前>、消すとき →npm uninstall <名前>(どちらもロックファイルが更新されるので、package.jsonと一緒にコミットする) - clone した直後や、ほかの人の変更を pull したあと →
npm ciを使うと、記録どおりのバージョンがそろう - CI や本番の build →
npm ci
なお npm ci は、実行のたびに node_modules を作り直すので、そのぶん毎回少し時間がかかります。package.json とロックファイルがそろっていれば、手元では普段 npm install を使っても記録どおりの版が入ります(上の表の1行目)。
ライブラリの場合は事情が違う
npm に公開する「ライブラリ」を作っている場合は事情が違います。ライブラリを使う側には、ライブラリの package-lock.json は配られないので、利用者の手元での動きを固定する役には立ちません。ただ、ライブラリでも開発する人どうしや CI の環境をそろえる目的でコミットすることはあり、方針が分かれます。この記事の話は、Webアプリのように「そのリポジトリ自体を動かす」場合の話です。
AI に依存関係を触らせるときの確認ポイント
AI が npm install <名前> を実行すれば、package.json と package-lock.json の両方が書き換わります。ただし package.json を直接書き換えただけだと、ロックファイルは古いままです(さっき npm ci が止まった状態)。ロックファイルの差分は大きいので見るのを飛ばしたくなりますが、次の点だけ確認しておくと安心です。
-
package.jsonの変更と、package-lock.jsonの変更が、同じコミットに入っているか - 頼んでいないパッケージの指定(バージョンの範囲)まで書き換わっていないか(
git diff package.jsonで確認できる) - 手元で
npm ciが通るか(ロックファイルの更新漏れがあれば、ここで止まる)
まとめ
-
package.jsonの^は範囲指定なので、それだけでは入るバージョンが決まらない -
package-lock.jsonがあると、記録どおりのバージョンが入る(試した例では 2.0.0 のまま。ロックファイルとnode_modulesを消すと 2.1.3 が入った) -
npm ciはロックファイルどおりに入れ、食い違いやロックファイルがないときは止まる - アプリのリポジトリでは、差分がうるさくても
package-lock.jsonはコミットする
未経験者向けの講座を運営しています
未経験から Next.js + Supabase + Claude Code で Webアプリを公開するまで を、全24セッションで体系化した教材です。Claude Code を学習パートナーにする CLAUDE.md と学習モード(learner / developer)の設計までセットで含みます。
- ▶ ライブデモ(インストール不要・無料で今すぐ見られます)→ https://trial-web-ayies128s-projects.vercel.app/?ref=qiita
- 教材完全版+月5,500円のメンタリング(全24セッション+チャット質問し放題)→ https://menta.work/plan/20251?ref=qiita
- 無料体験版(git clone して自分の手元で動かす・最初の数セッション分・⭐ Star もよろしくお願いします)→ https://github.com/ayies128/next-ai-camp-trial
- YouTube『AIエンジニア情報局』(AI×開発ニュースを1本5分でキャッチアップできる別運営チャンネル・無料)→ https://www.youtube.com/channel/UC1rXVD9WYsQPQEWZyd-A1KA/?ref=qiita
※ 中上級者には易しすぎる内容なので、初心者の知り合いへの紹介や社内研修の参考としてどうぞ。