Monorepo

What is a Monorepo?

A monorepo -- short for monolithic repository -- is a software development strategy where multiple projects coexist in a single version control repository. Instead of maintaining separate repositories for the frontend, backend, shared libraries, design system, and infrastructure code, everything lives together. Each project retains its own directory, dependencies, build process, and deployment pipeline, but they share a common version history and can reference each other directly.

A monorepo is not a monolith. The code inside can be organized as independent microservices, packages, or applications that are built and deployed separately. The "mono" refers to the repository, not the architecture. Google, Meta, Microsoft, and Uber all use monorepos to manage thousands of projects and billions of lines of code.

Why does it matter?

The alternative to a monorepo is a polyrepo strategy -- one repository per project or package. Polyrepos provide clear boundaries and independent version histories, but they create friction at every integration point. Shared code must be published as packages, versioned, and consumed through a package registry. A change that spans multiple repositories requires coordinated pull requests, synchronized releases, and careful dependency version management. Diamond dependency conflicts -- where two packages depend on different versions of a shared library -- become an ongoing tax on development time.

Monorepos eliminate this coordination overhead. A developer making a change to a shared library sees immediately -- in the same pull request -- how that change affects every consuming application. If the API of an internal package changes, all consumers are updated atomically. There is no version drift, no "which version of the shared library does this service use?" confusion, and no publishing ceremony for internal packages.

Cross-project refactoring becomes practical. Renaming a function, changing an interface, or restructuring a module can be done across the entire codebase in a single commit. Code reviews capture the full context of a change -- the library modification and its impact on consumers -- in one diff. This atomic visibility is particularly valuable for maintaining TypeScript type safety across package boundaries.

For organizations scaling their engineering teams, monorepos provide a unified developer experience. Tooling -- linters, formatters, test runners, CI/CD pipelines -- is configured once and shared across all projects. New team members set up their development environment once, not per-project. Coding standards are enforced globally rather than diverging across repositories. The cost of onboarding a new developer is lower, and the consistency of the codebase is higher.

Monorepo in practice

Modern monorepo tooling has made the approach practical for teams of all sizes. Tools like Turborepo, Nx, Lerna, and pnpm workspaces provide the infrastructure needed to manage multiple packages in a single repository efficiently.

A typical monorepo for a SaaS product might be structured as follows:

apps/
  web/          # Next.js frontend
  dashboard/    # React admin panel
  api/          # Node.js backend
packages/
  ui/           # Shared design system components
  types/        # Shared TypeScript types
  utils/        # Common utility functions
  config/       # Shared ESLint, TypeScript, Prettier configs
infrastructure/
  terraform/    # IaC definitions

The apps/ directory contains deployable applications. The packages/ directory contains shared libraries consumed by the applications. Each package has its own package.json with defined exports, and applications reference packages through workspace dependencies (e.g., "@company/ui": "workspace:*").

Build orchestration is critical for monorepo performance. Turborepo and Nx analyze the dependency graph and build only what has changed. If a developer modifies the ui package, only the ui package and the applications that depend on it are rebuilt and tested. Unchanged packages are served from a local or remote cache. This selective execution keeps build times manageable even as the repository grows.

CI/CD in a monorepo uses affected-project detection. When a pull request modifies files in packages/ui/, the pipeline runs tests for the UI package and every application that imports it. Changes to apps/api/ trigger only the API pipeline. This targeted execution prevents the common concern that "building everything on every change" makes monorepo CI slow. In practice, well-configured monorepo CI is faster than polyrepo CI because shared dependencies are built once rather than once per consuming repository.

The tradeoff is tooling complexity. Repository-level operations like cloning, searching, and git operations slow down as the repository grows. Sparse checkouts, shallow clones, and file system monitors mitigate these issues. Access control -- restricting who can modify which directories -- requires additional configuration since git does not natively support per-directory permissions. For most teams building products with fewer than a million lines of code, these concerns are manageable with standard tooling.

Related concepts

We respect your privacy

This website uses cookies for essential functions and optionally for analytics and marketing. Privacy Policy