Skip to content

Single Version Policy

A while back, we hit a wall that's all too familiar in monorepo life: dependency chaos. It was a classic case of "One team upgrades a library, another lags behind, and suddenly the same codebase runs three versions of React. Debugging becomes archaeology. Upgrades turn into whack-a-mole. Everyone feels the pain."

We needed a new approach—one that would bring order, predictability, and speed. So we made a bold move: one version to rule them all.

Why Single Version?

If a dependency is shared, its version should be too.

A Single Version Policy (SVP) means every package in the monorepo shares the same version of any given dependency. This approach unlocks powerful benefits for teams working together at scale:

  • Consistency: No more version drift or mysterious bugs from mismatched libraries
  • Simplicity: Upgrades become a single change, not a scavenger hunt
  • Smaller builds: No duplicate dependencies bloating your bundles
  • Confidence: Everyone knows exactly what's running in production

The result? Fewer surprises, faster reviews, and a codebase that feels like a team effort—not a patchwork.

How We Made It Work

The key was making the right thing easy. We used pnpm's catalog feature to define all external dependency versions in one place:

catalog:
  react: 18.2.0
  typescript: ^5.0.0
  @types/node: ^20.0.0

Inside each package, dependencies point to the catalog:

"dependencies": {
  "react": "catalog:"
}

Adding a new dependency? Run pnpm add from the package directory, then standardize it with a codemod (pnpm dlx [email protected] pnpm/catalog).

Internal packages use the workspace: protocol, keeping everything in sync:

"dependencies": {
  "@your-org/utils": "workspace:^"
}

External = catalog:
Internal = workspace:^

No guesswork. No drift.

Guardrails: Linting for Safety

A policy is only as strong as its enforcement. We wrote a custom ESLint rule to check every package.json:

  • All external dependencies must use catalog:
  • All internal dependencies must use workspace:

CI enforces it. If someone tries to sneak in a pinned version, the build fails. It's not glamorous, but it's essential.

Tradeoffs and Lessons

SVP isn't magic; it means recurring coordinated upgrades—sometimes you need to fix several packages at once. But it's a single, visible change, not a slow drift. And, the investment up front pays off in long-term sanity. For rare edge cases, we allow a second named catalog (like next-react), but only as an exception.

SVP isn't just a rule—it's a foundation. It gives the team speed, safety, and focus. We wouldn't go back.

Edited on

There is no need to use codemod as pnpm now supports catalogMode.

packages:
  # ...
catalogMode: strict
catalog:
  # ...

Then, you can add dependencies using pnpm add as usual, and they will automatically be added to the catalog.

Comments