Skip to content

Monorepos: shared code, explicit boundaries

By Published

My earlier explanation of monorepos used a forest as a metaphor for projects sharing resources. That is a useful starting point, but it leaves out the decision that matters: which parts of the code should change together, and which parts need to remain independent?

A monorepo holds multiple projects in one repository. It can make a shared change easier to inspect and coordinate. It does not, by itself, make the projects modular, align their dependency versions, or make every deployment safe.

What is actually shared?

A repository can contain applications alongside the libraries they use. The original directory structure makes that relationship easier to see:

Plain text
root/
├── packages/
│   ├── ui-components/
│   ├── shared-utils/
│   └── core-services/
├── apps/
│   ├── web/
│   ├── mobile/
│   └── admin-dashboard/
└── package.json

The applications can depend on shared packages without reaching into one another’s implementation. A shared UI package is useful when its consumers need the same behavior. Putting unrelated code into shared-utils simply because it fits there creates a dependency without a clear purpose.

Turborepo’s package model distinguishes deployable applications from the libraries that support them. That distinction is more useful than a folder convention alone: it helps describe what can depend on what.

One commit is not one deployment

An atomic source change can update a library and its consumers in the same commit. A reviewer can inspect the interface change, callers, and tests together rather than coordinating several repository changes.

That is a source-control boundary, not a distributed transaction. Applications may still deploy at different times. If a browser client, service, or database schema changes, a mixed-version period may still exist. Design compatibility and rollout around the deployed system, not the fact that its source shares a repository.

For a shared function used only inside a build, changing all its callers together may be enough. For a service API used by independently deployed applications, an additive change followed by a later removal may be safer. The repository does not remove that distinction.

Shared dependencies still need a policy

The original article described a single source of truth for dependencies. More precisely, one workspace gives a team a place to coordinate versions. Packages can still request different versions, and shared code can still introduce breaking changes.

Decide which dependencies need to stay aligned, which packages may be versioned independently, and how a shared update will be checked against its consumers. A lockfile records resolution; it is not evidence that every consumer behaves correctly.

Enforce the boundaries you draw

A package should declare what it depends on and expose the interface its consumers are meant to use. Cross-package imports that bypass that interface make refactoring harder, even though all the files are easy to reach.

Automated boundary checks can detect undeclared dependencies and imports outside a package. Turborepo’s boundaries command is currently experimental. Whatever tool a team chooses, ownership and review rules still need to be clear.

Security boundaries need separate treatment. A server-only identity helper, for example, should not become a browser dependency because it lives in a convenient shared package. The authentication synchronization example depends on enforcing identity and permissions where the operation runs.

Account for the cost of shared tooling

A larger repository can increase checkout time, indexing work, and the cost of naive CI. Dependency-aware checks and build caching can help, but only when the build graph describes the inputs that actually affect an output. A cache hit is useful evidence only if the cache key is sound.

The organizational cost matters too. Someone needs to maintain shared configuration, review cross-cutting changes, and help teams move when an interface changes. Centralizing files without funding that work can centralize the bottleneck.

Choose around the work, not the label

A monorepo is worth considering when related projects frequently change together and teams benefit from a shared view of their interfaces. Separate repositories can be a good choice when access requirements, ownership, or release coordination make that separation useful. Neither layout guarantees independence or collaboration.

Before migrating, look at a few real changes. How many projects did each touch? Where did coordination stall? Which tests would establish that the shared change was safe? Those answers are a better basis for the decision than the repository style of a much larger company.

The same questions matter in AI-assisted engineering: a clear module interface and useful feedback let implementation move without asking a reviewer to reconstruct the entire system. More shared code should not mean more hidden context.

First published January 8, 2024 as “Understanding Monorepos Through a Forest Metaphor.” Revised October 26, 2026 to restore the missing directory example, remove blanket dependency and deployment claims, and make the engineering tradeoffs explicit.