Should your frontend, backend, shared libraries and infrastructure code live in one repository (monorepo) or several (polyrepo)? Both work at every scale: Google and Meta run giant monorepos, and many successful companies run hundreds of small repositories. The right choice depends on how your code changes together, and how your team works.

At a glance

Monorepo Polyrepo
Cross-project changes One commit / pull request Several coordinated PRs and releases
Code sharing Import directly Publish packages, or use submodules
Dependency versions Easy to keep consistent Can drift between repos
CI Needs “only build what changed” logic Simple per repo
Access control Coarser (whole repo, plus CODEOWNERS) Per repo
Deploy independence Possible, but needs discipline Natural
Repo size and clone time Grows large Small
Tooling Nx, Turborepo, pnpm/yarn workspaces, Bazel, Pants Standard Git workflows

Monorepo strengths

  • Atomic changes: update an API and all its callers in one pull request.
  • Easy refactoring and code reuse across projects.
  • One place for tooling, linting, CI configuration and documentation.
  • Simpler onboarding: clone once, see everything.

Monorepo costs

  • CI must be smart: build and test only affected projects, or pipelines get slow. Path filters (paths: in GitHub Actions, rules: changes: in GitLab CI) or tools like Nx/Turborepo do this.
  • Permissions: everyone with access sees everything. CODEOWNERS files control reviews, not reading.
  • Size: large histories, and binary assets. Use shallow or partial clones, and Git LFS for big files. See Git LFS.

Polyrepo strengths

  • Independence: each service has its own release cycle, CI, and permissions.
  • Clear ownership boundaries for teams.
  • Small, fast repositories.

Polyrepo costs

  • Coordinated changes across repos are slow (version bumps, multiple PRs, release ordering).
  • Duplicated configuration (CI, linting) drifts unless templated.
  • Shared code needs a package registry (npm, PyPI, Composer, Go modules), or submodules. See Git submodules.

What fits small teams

For a small team building one product (web app + API + workers + infrastructure code):

  • a monorepo is often simplest: one PR for a feature that touches the frontend and backend, one CI configuration, shared types;
  • keep deployments independent with path-based CI triggers, and separate Docker images per service. See GitHub Actions Docker builds.

Polyrepos make more sense when parts are owned by different teams, have different access requirements (open-source library vs private app), or have truly independent lifecycles.

Example monorepo layout

.
├── apps/
│   ├── web/          # frontend
│   ├── api/          # backend service
│   └── worker/
├── packages/
│   ├── ui/           # shared components
│   └── types/        # shared TypeScript types
├── infra/
│   ├── ansible/
│   └── terraform/
└── .github/workflows/

CI example (GitHub Actions) building the API only when it changes:

on:
  push:
    paths:
      - "apps/api/**"
      - "packages/types/**"

See CI/CD pipeline explained and Linux configuration management.

Migrating

  • Polyrepo → monorepo: git subtree add or git filter-repo let you import repositories with history into subdirectories.
  • Monorepo → polyrepo: git filter-repo --subdirectory-filter apps/api extracts one project with its history.

Quick reference

If… Prefer
One product, one small team Monorepo
Shared types/libraries change often Monorepo
Separate teams, separate release cycles Polyrepo
Different access needs (public vs private) Polyrepo
Monorepo CI too slow Path filters, Nx/Turborepo/Bazel

Related: Git branching strategies and deployment strategies.

  • Git
  • DevOps
  • Architecture