"I just wanted three apps to share a few TypeScript types, and I burned half a day on Yarn workspaces config." If you ship iOS, Android, and web in parallel as a solo developer, that hour-vampire feels familiar. I bounced between Yarn and npm workspaces for a while, but switching to pnpm cut my "dependency mishap" rate by roughly 80%. This guide walks through how I bootstrap a pnpm monorepo with Antigravity assisting on the heavy lifting, including the traps I hit personally so you can sidestep them.
Why I picked pnpm — what tipped me off npm and Yarn
Honestly, the trigger was running out of disk space. With several apps on one machine, node_modules directories pile up into gigabytes fast. pnpm stores every package once in a content-addressable global store and then hardlinks each project's files to it. Five projects on the same React 19.x release? Still one physical copy on disk. You get the SSD savings and faster installs as a package deal.
The bigger win, though, is strict phantom-dependency detection. If you import a package that isn't declared in package.json, pnpm refuses to resolve it. That feels strict the first time it bites, but in my experience it surfaces issues at install time instead of the day you finally cut a release branch — which is the kinder failure mode. I used to find these issues in CI after merging to main; now I find them before the first commit.
There's also a less obvious benefit: pnpm's lockfile diff stays much smaller than Yarn's yarn.lock. Code reviews on dependency upgrades are easier to scan, and conflict resolution is far less painful when two branches both touch dependencies. For a solo developer who reviews their own diffs, that's quietly a quality-of-life upgrade.
Let Antigravity scaffold the repo
Antigravity's Plan Mode is the fastest way to sketch the entire structure in one pass. I usually open with a brief like this:
Project name: my-product
Layout:
- apps/web (Next.js 16 App Router + TypeScript)
- apps/mobile (Expo 54 + React Native 0.81)
- packages/ui (shared UI components)
- packages/config (shared ESLint/TypeScript/Tailwind preset)
- packages/types (DB and API domain types)
Constraints:
- Use pnpm workspaces
- Pin Node 22 LTS and pnpm 10 in engines
- packages/config presets are extended by apps/* and packages/ui
- Make circular references between apps and packages physically impossible
Approve the plan once, then switch to Fast Mode so file generation and edits run in parallel. The plan-then-execute flow matters more than it looks: Plan Mode forces Antigravity to surface assumptions you'd normally only notice halfway through the work, and Fast Mode then keeps you out of approval fatigue. If you want to go deeper on the trade-offs between the two modes, the Plan Mode and Fast Mode practical guide is worth a read.
The minimum root config — pnpm-workspace.yaml and .npmrc
Don't outsource this part entirely. Reviewing it yourself pays off later.
# 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*Keep shamefully-hoist=false. Flipping it to true breaks pnpm's "transparent dependency tree" promise and lets phantom dependencies slip back in. Selectively hoist tooling like ESLint and types via public-hoist-pattern so editor Quick Fix actions still light up. I forgot this once and lost a full day to "the editor flags an error but CI is green."
auto-install-peers=true is the other knob worth understanding. Without it, every Tailwind, Storybook, or testing-library package will spam warnings during install. With it, pnpm pulls in the declared peer ranges automatically, which keeps the install phase clean enough that you'll actually read the warnings that matter.
Stitching packages together with workspace:*
The fiddly part of any monorepo is wiring packages with the workspace:* protocol. Hand-edits invite version drift. Open a package.json and trigger Antigravity's Inline Chat (Cmd+I) with something like "wire packages/ui into apps/web using the workspace protocol." It will rewrite the relevant files for you.
// apps/web/package.json
{
"name": "@my-product/web",
"dependencies": {
"@my-product/ui": "workspace:*",
"@my-product/types": "workspace:*"
},
"devDependencies": {
"@my-product/config": "workspace:*"
}
}When changes span multiple files, lean on Antigravity's multi-file editing capability — it removes a surprising amount of manual coordination. After accepting the AI's diff, run pnpm -w build once to confirm every package still compiles. Catching a broken type on your laptop is much cheaper than catching it during a deploy. I also recommend wiring up a top-level pnpm -w typecheck script that runs tsc --noEmit across every workspace; it's the cheapest insurance policy you can write.
Three traps I actually hit
These aren't in the official docs, but they cost me real hours.
Expo and React Native fight pnpm's symlinks. Metro resolves modules against physical node_modules paths, so the default node-linker=isolated triggers Cannot find module errors all over the place. My fix is a per-app .npmrc at apps/mobile/.npmrc that overrides node-linker=hoisted. Ask Antigravity about the latest Expo + pnpm interplay before you commit — the situation moves fast and the right answer changes with each Expo release.
husky and lint-staged forget the working directory. When lint-staged sits at the repo root, you must pass pnpm --filter @my-product/web lint or the linter no-ops on a clean signature. The pnpm + Husky + lint-staged precommit guide shows the layout that actually runs in CI. The symptom is sneaky: lint-staged reports success because it ran zero matchers, and you only notice when ESLint errors hit production code review.
CI installs are slow without a cache. A naked pnpm install --frozen-lockfile in GitHub Actions is wasteful. Combine pnpm/action-setup with actions/cache keyed on the lockfile to share the pnpm store across runs. My current pipeline went from a 90-second cold install to roughly 12 seconds on cache hits. Cache the store directory itself (the path printed by pnpm store path), not just node_modules, because pnpm restoring from the store is materially faster than re-extracting tarballs.
A daily workflow that compounds
Once the structure is in place, the day-to-day rhythm matters more than the initial scaffold. Two habits make the biggest difference for me.
First, I prefix every Antigravity prompt with the workspace I'm focused on. Saying "in apps/web, refactor the auth provider" produces tighter diffs than "refactor the auth provider," because Antigravity then loads context only from that workspace. Plan Mode actually exposes which files it intends to touch before any edit happens, so a misrouted prompt is easy to catch. The cost of one extra phrase up front is much lower than untangling cross-app changes later.
Second, I treat pnpm -w as my universal entry point. Every common task — build, typecheck, lint, test — has a top-level script that fans out across packages. When Antigravity asks "which command should I run to verify this change?" I always have a one-line answer. This also makes onboarding a contributor or a new agent trivial: read package.json, run pnpm -w build, see green.
A practical example: I run pnpm -w deps (an alias for pnpm list -r --depth 0) every Monday morning to spot version drift between workspaces. When two apps end up pinning slightly different React Native patch versions, that's exactly the moment to nudge Antigravity toward consolidation rather than discovering the mismatch during a build break.
A third habit worth adopting: define a pnpm -w changeset script even before you publish anything. The Changesets workflow keeps a running record of which packages changed and how their versions should bump. Even on a private monorepo where you never release to npm, that history is gold for understanding why a package changed three months later. Antigravity reads the changeset files as part of its repo context, so when you ask it to pick a sensible commit message it will naturally weave in the package-level intent. That alignment between human-authored changesets and AI-authored summaries is the kind of small detail that compounds across a year of work. You also get a no-drama path to actually publishing internal libraries to a private registry the day you decide to.
Wrap-up — what to do today
Have Antigravity draft pnpm-workspace.yaml and .npmrc, then read them yourself. After that, growing the monorepo is just "wire each new dependency with the workspace protocol" on repeat. Once that rhythm clicks, the next move is consolidating your ESLint, Biome, and TypeScript presets into packages/config. The Biome lint and format unification guide is a clean follow-up.