Monorepo

A Monorepo means that all projects, libraries, and components are stored in a single repository. This approach enables unified builds, simplified version coordination, and shared infrastructure.

✅ When is it appropriate

A Monorepo is a good choice if most of the following apply:

  • multiple projects or services share common libraries, and keeping those libraries in sync across separate repositories is causing problems
  • projects are tightly coupled and a change in one component frequently requires coordinated changes in others
  • a single CI/CD pipeline that builds and tests everything together is easier to manage than separate pipelines per repository
  • all projects use the same toolchain, coding standards, and dependency versions and the team wants to enforce that consistently
  • large-scale refactoring that touches many projects at once is common, and doing it across separate repositories would be error-prone

A monorepo removes the overhead of keeping shared libraries consistent across many separate repositories. When a library is updated, all projects that depend on it are updated in the same commit, and a single pipeline can verify that nothing broke.

❌ When is it NOT appropriate

A Monorepo may not be ideal if:

  • the projects are entirely independent with no shared code and no need for coordinated releases
  • different teams need to release on completely separate schedules without any coordination
  • teams need strict access isolation, where one team must never be able to see or change another team's code
  • the toolchain does not support incremental builds, so every commit triggers a full rebuild of all projects regardless of what changed

A monorepo works best when projects have reasons to be together. Placing fully independent projects with no shared code into one repository creates coordination overhead without any of the benefits.

👍 Advantages

  • a shared library update is verified against all dependent projects in one commit, so broken dependencies are caught immediately rather than discovered later in separate repositories
  • a change that spans multiple projects is a single pull request, making it easy to review everything together and roll back if something breaks
  • one CI/CD pipeline configuration covers all projects, reducing the effort of maintaining separate pipeline files per repository
  • all projects use the same linting rules, test setup, and tooling configuration, so there is no drift between repositories
  • shared helper code lives in one place and is imported directly without publishing it as a versioned package first

👎 Disadvantages

  • git operations such as clone, fetch, and log become noticeably slower as the repository grows to hundreds of thousands of commits or gigabytes of history
  • without incremental build tooling, every commit rebuilds and retests all projects even when only one small file changed
  • restricting repository access so that one team cannot read another team's code requires careful configuration that most git hosting platforms make cumbersome
  • the CI/CD system must be configured to run only the pipelines relevant to what changed, which is more complex than one pipeline per repository
  • teams used to owning a dedicated repository may resist the shared ownership model that a monorepo requires

🛠️ Typical use cases

  • small to medium-sized teams
  • tightly-coupled microservices or shared libraries
  • frontend + backend mono-repositories
  • internal projects requiring centralization
  • platforms with frequent refactoring or shared code

⚠️ Common mistakes (anti-patterns)

  • not configuring incremental builds so that every commit triggers a full rebuild of all projects, causing CI pipelines to take many minutes even for a one-line change
  • placing all code in a flat folder structure without separating projects into clearly named directories, making it hard to understand what each part does or who owns it
  • not defining which team owns which directory, so any developer modifies any project and breaking changes accumulate without review
  • running all tests on every commit regardless of which project changed, so test suites that should take seconds take minutes and slow down the entire team
  • forcing large teams with completely independent products into a monorepo, creating merge conflicts and pipeline contention without any of the shared-code benefits

The most painful anti-pattern is a monorepo without incremental builds. As the repository grows, every small commit triggers a full rebuild of everything, and CI pipelines that once ran in two minutes can grow to thirty. Tools like Nx, Turborepo, or Bazel solve this by tracking which files changed and running only the affected builds and tests.

💡 How to build on it wisely

Recommended approach:

  1. Organise code into clearly named directories per project or library, and use a consistent naming convention so it is immediately clear what each folder contains and who owns it.
  2. Configure incremental builds so that only the projects affected by a change are rebuilt and retested; tools like Nx, Turborepo, or Bazel can do this automatically.
  3. Use a monorepo management tool such as Nx or Turborepo to track dependencies between projects and enforce that a change in one does not silently break another.
  4. Clearly define access rights and ownership.
  5. Regularly refactor and maintain clean builds.

A monorepo is the right choice when shared code and coordinated changes across projects justify the additional tooling. The signal to reconsider is when projects have no shared code, teams need completely independent release schedules, or CI pipelines are growing uncontrollably because incremental builds have not been set up.

Feedback & Sharing

Give us your thoughts on this page, or share it with others who may find it useful.

Share with your network:

Feedback

Found this helpful? Let me know what you think or suggest improvements 👉 Contact me.