Moving from scattered repositories to a monorepo is one of the highest-leverage infrastructure changes a frontend team can make. It’s also one of the easiest to get wrong. Getting it right means understanding not just the tooling but the migration strategy, the CI changes, and the workflow shifts your team will need to absorb.

Why monorepo?

The case for a monorepo is straightforward: atomic changes across packages. When your shared components, your applications, and your utilities live in separate repositories, a single feature might require three PRs, three review cycles, and careful coordination of release timing. One missed step and you have broken consumers.

In a monorepo, that’s one PR. One review. One merge. The dependency graph is visible. The refactoring is atomic. The productivity gain is real and immediate.

But there’s a deeper benefit that takes longer to appreciate. In a monorepo, you can grep for usage. You can see exactly how your shared components are consumed, which patterns are common, and which APIs are confusing engineers. That visibility is invaluable for making good architecture decisions.

Phase 1: Audit and map dependencies

Before touching any tooling, map your current dependency graph. Which packages depend on which? Where are the circular dependencies? What’s the actual publish and consume flow?

I use a simple spreadsheet for this: package name, current repo, internal dependencies, external consumers, publish frequency. This map becomes your migration order. You migrate leaf packages first (the ones with no internal dependencies) and work inward toward the packages that have the most dependents.

This audit phase also surfaces problems you’ll need to solve before migrating. Common findings:

Version mismatches: different repos using different versions of the same core dependency. You’ll need to align these before or during migration.

Circular dependencies: package A depends on package B, which depends on package A. These need to be untangled before they can live in the same monorepo, because the build system will choke on them.

Implicit dependencies: packages that work because of hoisted node_modules rather than explicit dependency declarations. These will break in a monorepo with strict dependency isolation.

Phase 2: Choose your tooling deliberately

The monorepo tooling landscape is noisy. Here’s how I think about the choices in 2025:

Package manager: pnpm

Use pnpm workspaces. Its strict dependency isolation (packages can only access explicitly declared dependencies) catches bugs that npm and yarn silently allow through hoisting. Its content-addressable store means faster installs and less disk usage. The workspace protocol (workspace:*) makes internal dependency management clean.

If your team is deeply invested in npm or yarn and a package manager switch adds too much friction, both work fine with workspaces. But pnpm is the better foundation for a new monorepo.

Task orchestration: Turborepo or Nx

Turborepo if you want simplicity. It does one thing well: run tasks in the right order, skip work that hasn’t changed, and cache everything aggressively. Configuration is minimal. The mental model is small. Remote caching on Vercel is free and unlimited.

Nx if you need fine-grained computation caching, dependency graph visualisation, enforced architectural rules, and task distribution across multiple CI machines. Nx is a more comprehensive platform with more features, but also more concepts to learn and more configuration to manage.

My default recommendation: start with Turborepo unless you have a specific need that only Nx addresses. You can always migrate to Nx later. Going the other direction is harder.

Versioning: Changesets

For libraries that need to be published, Changesets is the right tool. It separates the intent to release (a developer declares what changed and whether it’s a patch, minor, or major) from the act of publishing (an automated process that bumps versions, updates changelogs, and publishes to npm).

This workflow matters because it makes versioning a team activity rather than a bottleneck. Any developer can declare their change’s impact. The release automation does the rest.

Phase 3: Migrate incrementally

This is where most teams go wrong. They plan a big-bang migration weekend where everything moves at once. Don’t do this.

The incremental approach:

  1. Set up the monorepo structure with one package. Pick a leaf package (no internal dependents). Move it into the monorepo. Get CI working. Get the local development workflow feeling right.

  2. Run it in production for two weeks. Let the team get comfortable with the new workflow. Fix the rough edges. Make sure the CI pipeline is fast enough, that PR reviews work smoothly, and that developers aren’t fighting the tooling.

  3. Migrate the next package. Pick the next leaf. Move it in. This time it should be faster because the infrastructure already exists.

  4. Repeat. Each migration is small enough to revert if something goes wrong. The team builds confidence incrementally. CI adapts gradually. Documentation grows organically.

The typical mistake with big-bang migrations isn’t the migration itself. It’s that nobody discovers the workflow problems until everyone is stuck with them. Incremental migration lets you find and fix problems when only one team is affected.

Preserving git history

One detail that matters more than you’d think: preserve git history when moving code into the monorepo. git log and git blame are how engineers understand why code exists in its current form. Losing that history means losing context.

Use git filter-repo or a similar tool to rewrite the history of each source repo so that all files appear under their target monorepo subdirectory. Then merge these rewritten histories into the monorepo with git merge --allow-unrelated-histories. The result is a clean history where git log -- packages/my-package/ shows the full history from before and after the migration.

This takes more effort than copying files over, but the payoff is permanent.

Phase 4: Fix the workflows

A monorepo changes how engineers work day to day. These workflow changes deserve as much attention as the technical migration.

Code ownership

Use CODEOWNERS files to define who is responsible for what. In a monorepo, every directory should have a clear owner. This serves two purposes: automated PR routing (GitHub/GitLab assigns the right reviewers automatically) and clarity about who to ask when you have questions about a particular package.

Structure your CODEOWNERS around team boundaries, not individual engineers. People change teams. Team ownership is more durable.

Affected-only CI

The most important CI optimisation for a monorepo: only run tests, linting, and builds for packages that were actually affected by a given change. Both Turborepo and Nx support this out of the box.

Without affected-only CI, your pipeline runs every test in the repo on every PR. For a small monorepo this is fine. For a large one, PRs will take 30 minutes to get CI results and engineers will start ignoring the pipeline.

Turborepo’s --affected flag (available since v2.1) analyses the git diff and only runs tasks for changed packages and their dependents. Combined with remote caching, this means most PR pipelines complete in under five minutes even for large monorepos.

Contribution guidelines for shared packages

When multiple teams contribute to shared packages, you need clear guidelines:

  • How to propose a change (RFC for significant changes, PR for small ones)
  • How breaking changes are handled (deprecation period, migration guide, major version bump)
  • Who reviews what (CODEOWNERS for routing, platform team for shared packages)
  • How to document changes (Changesets for version intent, conventional commits for commit messages)

Write these down in a CONTRIBUTING.md at the root of the monorepo. Keep it short and specific. Engineers will read a one-page guide. They won’t read a ten-page playbook.

Phase 5: Optimise

Once the migration is complete and teams are comfortable, invest in optimisation:

Remote caching: both Turborepo and Nx support caching task outputs remotely. This means that if one developer (or CI run) has already built a package, every subsequent developer who pulls the same inputs gets the cached result instantly. This alone can cut CI times by 50-70%.

Workspace constraints: tools like Nx’s boundary rules let you enforce architectural constraints. For example: application packages can depend on library packages, but not the other way around. UI packages can’t import from server packages. These rules catch architectural violations at lint time rather than in code review.

Automated dependency updates: tools like Renovate or Dependabot work well with monorepos and can batch updates across all packages into a single PR. Combined with affected-only CI, this keeps dependencies fresh without creating noise.

Common pitfalls

Migrating code and tooling simultaneously. Move the code first. Get the basics working. Then optimise the tooling. Trying to do both at once creates too many variables when something goes wrong.

Not cleaning up before migration. Large assets, build artifacts, and unused dependencies in the source repos will bloat the monorepo. Audit and clean up each repo before moving it.

Underestimating the cultural shift. Engineers who are used to owning an entire repo need to adjust to shared ownership. CODEOWNERS helps, but the real adjustment is cultural: understanding that changes to shared packages affect other teams, and that coordination is part of the workflow.

Skipping the incremental approach. I’ve seen teams spend a weekend migrating 15 repos into a monorepo and then spend two months fixing the CI pipeline, the local dev experience, and the dependency conflicts. The weekend migration felt fast. The cleanup was anything but.

The long view

A well-run monorepo is one of the best productivity multipliers for a frontend organisation. Atomic changes, shared visibility, enforced architectural boundaries, fast CI with caching. The benefits compound over time.

But the migration itself is a project, not a task. Treat it with the same planning and incrementalism you’d apply to any significant architectural change. Move slowly, validate at each step, and invest in the developer experience. The monorepo is only as good as the workflows that surround it.