Akash DahadProduct engineer · India

Thirteen repos, one product: why we moved to a monorepo

Field notes Frontend and the web 5 min read
NxMonorepoNext.jsNestJSDesign system

Twelve service repos and a frontend made every feature a multi-repo job. Why that slowed a small team, and what one monorepo with shared libraries changed.

In BHyve v1, one product lived in thirteen repos. Every feature touched three or four of them, and every deploy was its own ritual. For a small team, that overhead landed on the same few people every time.

Platform v2 put the core product in one repo. This is why the split slowed us down, what a monorepo with shared libraries changes, and how to tell whether a move like this is worth it.

The repos were split by service, but the work wasn’t

v1 was one Next.js frontend talking to twelve backend services. Each service was a NestJS app with its own repo, its own deploy and its own chart, the configuration that tells Kubernetes how to run it. NestJS is a framework for building server apps in TypeScript.

Splitting by service looks tidy on a diagram. The trouble is that work doesn’t arrive by service. A feature is usually a change to an API and to the screens that call it, and often to another service behind that API. When those live in separate repos, one change becomes several: separate pull requests, separate reviews, separate builds, and deploys that have to land in the right order.

Anything both sides need, like the shape of an API response, has to exist in each repo. The usual result is copies, and copies drift apart quietly until something breaks. And with a separate deploy per service, every release is a set of small rituals instead of one routine.

A large organisation with a team per service can absorb that. A small team feels all of it, because each of those costs lands on the same people.

The frontend had its own limit. It was built on Next.js’s pages router, the original way Next.js turns files into URLs, and on patterns that had reached the end of what they could carry.

The principle: keep together what changes together

If a feature always crosses the same boundaries, those boundaries are in the wrong place for your team. Put the things that change together in one place, share code instead of copying it, and have one way to release.

A monorepo, in plain words

A monorepo is one repository that holds many projects: apps, services and the libraries they share. It isn’t a monolith. Each app still builds and deploys on its own. What changes is that they live together, so one commit can change an API and every app that calls it.

The catch is scale. With everything in one place, a naive CI builds and tests everything on every change. That’s the problem Nx solves. Nx is a build system for monorepos. It works out which projects depend on which, runs tasks in the right order, caches results so it doesn’t rebuild what hasn’t changed, and can run tasks only for the projects a change affects.

What one repo with shared libraries changes

Platform v2 is one Nx monorepo: a Next.js web app, an iOS app, a Python AI service and the rest of the apps, built on shared libraries and one CI. I started the new web app in it. The difference shows up in four places.

  • One change, one pull request. An API change and every caller can move in one commit, reviewed together and tested together.
  • Shared code instead of copies. Types and common code live in libraries the apps import, so there’s nothing to keep in step by hand.
  • CI that builds only what a change touches. The shared libraries are built once and reused, and only affected apps rebuild. How we cut CI minutes by 71% explains the caching.
  • One release process. Merge to deploy, tag to release, the same for every app. That’s releasing by tag.

One more shared library mattered most to the daily work: the design system, a shared set of components and rules for how they look and behave. We designed the new interface as a system first, so every screen is built from the same parts. A new screen becomes assembly instead of invention, and a reviewer checks whether the right parts were used, not every pixel.

How to make the move

If you’re weighing the same change, this is the order the reasoning above points to.

  1. Map what changes together. List recent features and the repos each one touched. The repos that keep appearing together belong together.
  2. Pull shared code into libraries. API types, clients and UI components first, since they’re the copies that drift.
  3. Add a build tool that understands the dependency graph before the repo gets big, so CI only builds what a change affects.
  4. Give every app one release process. One pipeline, one way to promote to production.
  5. Design the interface as a system before building screens. Components first, screens after.
  6. Rewrite only where it changes how the team works. Moving code into one repo and rewriting it are separate decisions.

What it changed

The core product went from thirteen repos to one. Since the first production release, everything ships from one place through one tag-based process, about 1.7 releases a day.

The knock-on effects are in the daily work: a feature is one piece of work instead of a set of coordinated ones, CI spends its time on what changed, and screens come from the same parts. The old frontend has since gone quiet.

When a rewrite is worth it

A rewrite is only worth it if it changes how the team works. For us, the design system and the single release process mattered more than the framework upgrade.

Before you start one, ask:

  • Will it change how a feature gets built, or only what it’s built with?
  • Will it cut the number of places one change has to touch?
  • Will it give you one way to release?
  • Will it give every screen the same parts?

If every answer is no, you don’t need a rewrite. You need a framework upgrade, which is a much smaller job. More on Platform v2 is in the case study.

Quick answers

What is a monorepo?
A monorepo is a single version-control repository that holds many projects, such as apps, services and the libraries they share. Each project can still build and deploy on its own.
Is a monorepo the same as a monolith?
No. A monolith is one application deployed as a single unit. A monorepo is one repository that can hold many apps, each deployed separately.
What is Nx?
Nx is a build system for JavaScript and TypeScript monorepos, with plugins for other languages. It maps how projects depend on each other, caches task results, and can run builds and tests only for the projects a change affects.
Why do microservices in separate repos slow a small team down?
A feature usually changes an API and the code that calls it. In separate repos, that one change becomes several pull requests, builds and deploys that must land in the right order, and shared types have to be copied and kept in step by hand.
When is a frontend rewrite worth it?
When it changes how the team works, for example one release process or a shared design system, and not only the framework underneath. If the framework is the only problem, upgrade it instead.
The case study behind this post Platform v2: one monorepo instead of thirteen repos 13 → 1 repos for the core product