← Back to Insights

Monorepo Strategy: Nx, Turborepo, and Bazel Guide

Metasphere Engineering 8 min read

Your team migrated six repositories into a monorepo because someone in leadership came from a company that used one. Three months later, CI on main takes 45 minutes. Developers are pushing to feature branches and merging late to avoid triggering the full build. The teams that had been most autonomous before the migration are the loudest about reverting. Nobody set up affected-only task execution. Nobody configured remote caching. The monorepo was not the problem. The missing infrastructure was.

This exact story plays out regularly. The monorepo vs. polyrepo debate has generated far more opinions than evidence. Engineers who’ve worked in successful monorepos are passionate advocates. Engineers who’ve survived dysfunctional ones are equally passionate detractors. Both groups usually describe the same technical structure and draw opposite conclusions. That’s because repository structure does not determine success. Team structure and tooling discipline do.

Monorepo Atomic Commit vs Polyrepo Coordinated ChangesSplit-screen comparison showing a single atomic PR in a monorepo versus multi-day coordination chaos across polyrepos with version mismatch windows and runtime errors.Atomic Commit vs. Multi-Day CoordinationMonorepoPolyrepoSingle PR #472Modifies: libs/auth (breaking change)+ apps/billing + apps/orders + apps/users1 PRCI runs affected tests across all 4 packagesAll greenMerged. All consumers updated atomically.1 PR, 1 merge, 0 version mismatchesTotal elapsed time: 2 hoursPR #1: auth-lib repoBreaking change. Publishes v2.0.Day 1PR #2: billing-service repoUpdates auth-lib to v2.0. Reviewed, merged.v2.0Day 2PR #3: orders-service repoStill in review. Running v1.x.v1.xDay 4users-service repoUpgrade not started. Running v1.x.v1.xVersion mismatch window: 4+ daysSome services on v1, others on v2Runtime Error in orders-serviceIncompatible auth-lib version at runtimeMonorepoAtomic. 1 PR. Consistent state guaranteed.!Polyrepo4+ days of version drift. Runtime failures.The coordination cost scales with the number of repositories and teams involved.

What Monorepos Actually Solve

The core monorepo advantage is simple and powerful: atomic commits across project boundaries. When your authentication library changes in a way that breaks three services, that change happens in a single PR. Reviewers see the library change and all three service updates together. CI validates the full impact. The library and its consumers are always in a consistent state. There is never a window where the library is on v2 but two services are still running v1 because their teams haven’t gotten to the upgrade PR yet.

In a polyrepo, the same change requires publishing the library, then creating individual PRs in each consumer repository, then waiting for each team to review and merge on their own timeline. During that coordination window, some services run the old version and some run the new one. If the change is breaking in an unexpected way, it surfaces during an individual team’s upgrade, long after the library PR merged, with no easy path back. Anyone who’s managed a breaking library upgrade across 8 repos knows exactly how painful this gets.

The second monorepo advantage is code discoverability. When everything is in one place, engineers search the entire codebase before building something new. That shared utility someone wrote last quarter? It’s in libs/utils and your IDE finds it in seconds. In a polyrepo, it’s in a repository a new team member has never seen, listed in a wiki page nobody has updated since it was created. In practice, monorepo discoverability reduces duplicate implementations of common logic by 30-50% for teams that track it. That’s not a small number.

Tooling Requirements for Scale

A monorepo without task orchestration tooling does not scale past a few dozen packages. Full stop. The naive approach of running all builds and tests on every commit produces CI times measured in hours. The 45-minute CI that frustrated those developers is actually on the mild end. Monorepos without affected-only execution regularly reach 3 hours on main. Migrate to a monorepo without investing in the tooling and you will spend the next year explaining why the migration made everything slower.

Nx, Turborepo, and Bazel all provide affected-only execution. When a PR modifies libs/payments, only packages that depend on libs/payments are rebuilt and retested. Nothing else runs. Remote caching extends this further. If the same inputs produced the same outputs on another machine, the cached result is restored instead of recomputed. Developer laptops and CI runners share the same cache, so a build that passed on a colleague’s machine does not run again in CI. With this infrastructure in place, CI times typically decrease after a monorepo migration rather than increase. Teams report 60-90% CI time reduction from remote caching alone. That’s the difference between a monorepo that works and one that everyone hates.

Bazel offers the most powerful option: hermetic builds with complete dependency declaration and true reproducibility. It is also the most complex to configure and maintain, requiring dedicated platform engineering investment. Nx and Turborepo are significantly easier to adopt for JavaScript and TypeScript projects and support a wider range of project types with less configuration. Start there. Reserve Bazel for monorepos where build reproducibility is a compliance requirement or where the repository has grown past 500+ packages across multiple languages.

Module Boundaries and Ownership

Here is the organizational risk that sneaks up on you. Having everything in one place makes it easy for everything to depend on everything else. A service quietly imports a utility from another service’s internal module. A shared library grows unbounded as teams add domain-specific helpers. Over 6-12 months, hidden coupling accumulates until changes in one area trigger unexpected test failures across unrelated packages. Ownership becomes unclear. Nobody knows who’s responsible for that libs/common directory that 40 packages depend on. And everyone is afraid to touch it.

Module boundary enforcement prevents this at lint time. Nx’s enforce-module-boundaries ESLint rule defines which package types can import from which other types based on tags. A type:app package can import type:feature and type:lib. A type:lib cannot import type:feature. Domain packages can only import from their own domain or shared utility libraries. Violations surface in your editor while you’re typing, not during a production incident six weeks later.

CODEOWNERS files handle review ownership. Teams own their directories. Changes to shared libraries require review from the platform team. Design this deliberately. The ownership model needs to mirror your actual organizational structure, not be retrofitted after teams have already been stepping on each other’s contributions.

Here’s a practical tip that will save you months of pain: set up boundary enforcement before you migrate the second repository. The first migration is too small to feel the need. By the third, the coupling patterns are already established and retrofitting constraints means breaking existing imports. Every team that skips this step regrets it.

When Polyrepo Is the Right Answer

Polyrepo fits well when services have stable, versioned APIs and teams release on genuinely independent schedules. A financial services company where the payments team deploys on a strict change control window and the analytics team ships multiple times per day has genuinely different operational rhythms. Coupling those teams in a monorepo creates coordination overhead that did not exist before. That’s a net negative.

The microservice architecture principle of treating services as independently deployable units maps naturally to independent repositories when interfaces are genuinely stable. The qualifier is “genuinely.” Be honest with yourself here. Many teams believe their services are more independent than their actual frequency of cross-service coordination reveals. If you’re making coordinated changes across 3+ repositories more than twice a month, your polyrepo structure is fighting your actual coupling patterns.

The dependency management problem in polyrepos is solvable with the right tooling. Renovate and Dependabot automate dependency update PRs. Internal package registries with clear semantic versioning make version coordination explicit. The DevOps engineering investment required for polyrepo scale is different from monorepo scale, not necessarily higher. Polyrepos need cross-repo search, dependency dashboards, and release coordination tooling. Monorepos need affected-only CI, remote caching, and boundary enforcement. Total investment is roughly comparable. The question is which coordination problems your teams actually have, not which repo structure a company with 10x your headcount chose.

Design a Repository Strategy That Fits Your Team

Repository structure shapes how teams coordinate, how fast engineers can ship, and how painful cross-cutting changes are. Metasphere helps engineering organizations choose and implement the structure that matches their actual team topology - not what Google or Meta does.

Talk Repository Strategy

Frequently Asked Questions

What is the main advantage of a monorepo for a software company?

+

Atomic cross-project changes. When a shared library has a breaking change, you update all consumers in a single commit and CI validates everything together before merge. In a polyrepo, the same change requires coordinating PRs across multiple repositories and managing a window where some services run v1 and others run v2. Monorepos also make code discoverability trivial, reducing duplicate implementations across teams by 30-50% in organizations that measure it.

What tooling is required to make a monorepo work at scale?

+

Affected-only task execution and remote caching are non-negotiable. Without them, CI times grow linearly with repository size, reaching 30-60 minutes for 50+ packages. Nx, Turborepo, and Bazel compute the dependency graph and run only affected tasks. Remote caching reduces CI time by 60-90% by sharing build artifacts across machines and developers. Module boundary enforcement prevents the circular dependencies that make large monorepos brittle at the 100+ package scale.

How do you maintain code ownership in a monorepo?

+

CODEOWNERS files map directory paths to responsible teams. In a monorepo, apps/billing belongs to the billing team, libs/auth belongs to the platform team. Changes to those paths require review from the designated owner. Design the ownership model deliberately upfront. Without it, teams modify each other’s code without proper review, or informal social rules emerge that break down when teams grow beyond 30-40 engineers.

What does a polyrepo do better than a monorepo?

+

Polyrepos provide harder isolation boundaries. Teams can use different stacks, deploy independently, and manage access control per repository. A contractor can access one service repository without seeing others. If your teams genuinely operate with stable interfaces and independent release cadences, the coordination overhead of a monorepo often exceeds the benefit. The key word is ‘genuinely’ as many teams believe their services are more independent than they actually are.

Can you migrate from polyrepo to monorepo without losing git history?

+

Yes. Tools like git filter-repo and git subtree merge repositories while preserving full commit history. The practical challenge is migrating CI pipelines, deployment automation, and developer workflows. A typical migration of 10-20 repositories takes 4-8 weeks including tooling setup. Start with the 3-5 repositories that have the most cross-repo coordination overhead. They provide the highest payoff and prove the approach before you migrate the rest.