「3 つのアプリで TypeScript の型を共有したいだけなのに、Yarn workspaces の設定だけで半日溶けた」— 個人で iOS / Android / Web を並行開発していると、こういう罠を踏みやすいですよね。私もしばらく Yarn と npm workspaces を行き来していましたが、pnpm に切り替えてから「依存解決の事故」が体感で 8 割減りました。ここではAntigravity の AI 支援を組み合わせて pnpm モノレポを最短で立ち上げ、毎日の開発で恩恵を受けるまでの段取りを、実際に踏んだ落とし穴とあわせてまとめておきます。
なぜ pnpm を選ぶのか — 私が npm/Yarn から乗り換えた理由
正直に書くと、移行のきっかけは「ディスクが足りない」という単純な動機でした。複数アプリを抱えていると node_modules が GB 単位で積み重なります。pnpm は依存パッケージをコンテンツアドレス可能なグローバルストアに 1 度だけ保存し、各プロジェクトはハードリンクを張る方式です。同じ React 19.x を 5 プロジェクトで使っていても、ディスク上の実体は 1 つだけ。SSD の節約とインストール速度の両方が手に入ります。
ただし、利点はそれだけではありません。pnpm は厳格な phantom dependencies の検出を行います。package.json に書いていない依存を import すると壊れる、というのは初見だと面倒に感じるかもしれませんが、CI で初めて落ちる事故をローカルで先に潰せるという意味で、実は「やさしい挙動」だと私は思っています。
Antigravity でリポジトリ初期化を AI に任せる
Antigravity の Plan モードで、プロジェクト全体の骨格を一度に描いてもらうのが最速です。私は最初の依頼で次のように伝えています。
プロジェクト名: my-product
構成:
- apps/web (Next.js 16 App Router + TypeScript)
- apps/mobile (Expo 54 + React Native 0.81)
- packages/ui (共通 UI コンポーネント)
- packages/config (ESLint / TypeScript / Tailwind の共通設定)
- packages/types (DB と API のドメイン型)
要件:
- pnpm workspaces を使用
- Node 22 LTS / pnpm 10 を engines に固定
- packages/config のプリセットを apps/* と packages/ui から extends で参照
- apps と packages の循環参照を物理的に避ける構造
Plan モードで生成された設計案を 1 度承認してから Fast モードに切り替えると、ファイル生成と編集が並列で進みます。Antigravity の挙動を細かく制御したい方は、Antigravity の Plan モードと Fast モードを使い分ける実践ガイド も参考になります。
ルート構成の最小セット — pnpm-workspace.yaml と .npmrc
ここは AI に任せきりにせず、自分の目で必ずレビューしておきたい部分です。
# pnpm-workspace.yaml
packages:
- "apps/*"
- "packages/*"# .npmrc
auto-install-peers=true
strict-peer-dependencies=false
shamefully-hoist=false
node-linker=isolated
public-hoist-pattern[]=*types*
public-hoist-pattern[]=*eslint*shamefully-hoist=false を維持するのは大事です。これを true にすると pnpm の良さである「依存の透明性」が崩れ、phantom dependencies を許してしまいます。一方で、ESLint や型のような開発ツール系は public-hoist-pattern で限定的に持ち上げることで、エディタの Quick Fix が常用できる状態を保てます。私は最初これを忘れて「ローカルでは赤線が出るのに CI では通る」現象に 1 日溶かしました。
パッケージ間の依存解決を Antigravity に任せる
モノレポで一番面倒なのは、パッケージ間の依存を workspace:* プロトコルで正しく繋ぐ作業です。手で書くとバージョンの不整合が発生しがちですが、Antigravity の Inline Chat(Cmd+I)で package.json を開いた状態で「packages/ui を apps/web から workspace プロトコルで参照させて」と指示すると、対象ファイル群を自動で書き換えてくれます。
// apps/web/package.json
{
"name": "@my-product/web",
"dependencies": {
"@my-product/ui": "workspace:*",
"@my-product/types": "workspace:*"
},
"devDependencies": {
"@my-product/config": "workspace:*"
}
}複数ファイルにまたがる修正は、Antigravity の マルチファイル編集機能 を有効活用すると、ヒューマンエラーが激減します。Antigravity の AI が変更を提示してくれた直後は、必ず pnpm -w build を 1 度走らせて全パッケージのビルドが通るかを確認してください。型エラーで先に気づけば、本番デプロイで青ざめる場面を確実に減らせます。
私が実際に踏んだつまずきポイント
ここからは公式ドキュメントには書かれていない、私が個人プロジェクトで踏んだ罠を共有します。
ひとつ目は Expo / React Native と pnpm の相性です。Metro バンドラーは node_modules の物理ディレクトリを前提に解決を行うため、node-linker=isolated(pnpm の既定)のままだと Cannot find module が頻発します。私は Expo を含むワークスペースに限り、apps/mobile/.npmrc を作って node-linker=hoisted に上書きしています。Antigravity に「Expo の Metro が pnpm の symlink で壊れる」と質問すると、最近の RN バージョンに合った対処を提示してくれるので、最新の状況を確認するのがおすすめです。
ふたつ目は husky / lint-staged の相対パス問題です。モノレポで lint-staged をルートに置いた場合、pnpm --filter @my-product/web lint のように --filter を必ず付けないと、lint が空振りします。詳しい設定は pnpm + Husky + lint-staged のプリコミット運用ガイド で実例を確認できます。
みっつ目は CI の高速化です。GitHub Actions で pnpm install --frozen-lockfile を毎回フルで走らせるのではなく、pnpm/action-setup と actions/cache を組み合わせて pnpm ストアを共有するのが定石です。手元のプロジェクトでは初回 90 秒だったインストールが、キャッシュヒット時は 12 秒まで縮みました。
全体を振り返って — 次の一歩
最初にやるべきは、ベースとなる pnpm-workspace.yaml と .npmrc を Antigravity に書かせ、自分の目でレビューすることです。これだけ済ませれば、あとは「依存を増やすたびに workspace プロトコルで繋ぐ」という単純な作業の繰り返しに落とし込めます。pnpm モノレポに慣れてきたら、次は ESLint / Biome / TypeScript の共通設定を packages/config に集約する段階へ進むのが自然な流れです。共通設定の運用については Antigravity で Biome を使った lint / format の統一ガイド が参考になります。
私は本書で「分割の動機」を明文化してから、リポジトリのリストラに踏み切れました。