In Defence of the Monorepo
I used to think having more repositories meant I was doing Serious Software Engineering. One repo for frontend. One for the backend, One for shared types. Very professional. Very organized. Very “why do I need four pull requests to rename one button?”
Then I started working with monorepos, and now I am annoyingly difficult to convince otherwise.
What even is a monorepo?
A monorepo is one repository that contains multiple related projects.
It might look something like this:
apps/
web/
api/
mobile/
packages/
ui/
config/
definitely-not-a-random-utils-folder/The web app, API, mobile app, and shared packages all live together. They still have their own responsibilities and build processes; they just share one Git home.
This does not automatically mean the codebase is a monolith.
A monolith describes how software is designed and deployed. A monorepo describes where its source code lives. You can keep twenty independent services in one repository, or somehow create one giant tangled application across twenty repositories. Human creativity has no limits.
The point is not “put every file your company has ever created into one folder.” The point is to keep related code close enough that changing it does not require a small diplomatic mission.
Why I like monorepos
One Change Stays One Change
Imagine changing a shared API type.
In a multirepo setup, you might need to update the types package, publish a new version, bump that version in the API, bump it again in the frontend, open several pull requests, and wait for the correct planets to align.
In a monorepo, you update the type and every affected consumer in the same branch and pull request.
The reviewer can see the whole story at once. The change either works together or fails together. Nobody has to leave a comment saying, “This depends on PR number 847 in a repository you cannot access.”
That alone removes a surprising amount of ceremony.
Sharing Code Becomes Normal
Shared UI components, TypeScript types, validation schemas, lint rules, and configuration can live beside the apps that use them.
Good boundaries still matter, of course. A monorepo makes sharing easier; it does not make every piece of code worth sharing.
Sometimes duplication really is better than creating @company/universal-everything and appointing it ruler of the codebase.
One Language Across the Codebase
It is much easier to standardize formatting, linting, testing, TypeScript settings, and build conventions when everything lives together.
New projects can start with the same sensible defaults. Fixing a rule happens once. Upgrading a dependency can happen across every app in one coordinated change.
Without this, repositories tend to slowly develop their own cultures.
One uses tabs. One uses spaces. One is still on a Node version discovered in an archaeological dig. One has no linting because the person who created it “was going to add that later.”
Refactoring Gets Easier
Want to rename a shared function? Your editor can find every use across the repository.
Want to understand which app depends on a library? A good monorepo tool can show the dependency graph instead of asking you to develop psychic abilities.
When code is close together, it is easier to see the effect of a change and much harder to forget a consumer hiding in another repository.
Builds can be smarter
A monorepo does not mean rebuilding the entire universe every time someone changes a README.
Tools can understand the project graph, detect what changed, run only the affected tasks, and cache work that has already been completed. With the right setup, a large repository can feel faster than a collection of smaller repositories held together by shell scripts and optimism.
The important phrase there is with the right setup. We will return to that little warning shortly.
The Costs of a Monorepo
I like monorepos, but they are not a cheat code. They simply move the difficulty into different places.
The repo can become a junk drawer
Putting projects together is easy. Maintaining clear boundaries between them is the actual job.
Without conventions, every app starts importing random files from every other app. Shared packages become dumping grounds. Nobody knows who owns anything. Eventually, changing a button causes the billing service to rebuild for reasons known only to the moon.
A monorepo needs structure: named projects, explicit dependencies, ownership rules, and a firm stance against mysterious folders called common2.
Tooling is no longer optional
With three small packages, basic workspace support may be enough.
With fifty projects, you will want dependency graphs, task orchestration, caching, affected-project detection, and CI that understands the repository. Otherwise, every commit runs every test and everyone has enough time during CI to begin a second career.
The tools are powerful, but somebody has to understand and maintain them.
Permissions can get awkward
A single repository usually means everyone with repository access can see everything inside it.
That is fine for many teams, but not for code with strict security, legal, or client boundaries. You can add ownership and review rules, but those are not the same as completely separate access control.
If two projects genuinely must not be visible to the same people, putting them together for aesthetic reasons would be a spectacularly bad trade.
Releases need thought
Some monorepos release everything together. Others version and deploy every project independently.
Both can work, but you need to decide which world you live in. If every tiny library change publishes seventeen packages and triggers six production deployments, your automation has become an overly enthusiastic intern.
Independent versioning, change tracking, and release pipelines solve this, but again: more tooling, more rules, more things with YAML in them.
Git can feel the weight
Very large repositories can make cloning, history operations, and local tooling heavier. Most teams will not reach giant-tech-company scale, but binary assets, generated files, and careless dependency storage can make even a modest repo feel like it is carrying groceries up six flights of stairs.
Keep generated output out of Git. Keep large assets somewhere sensible. Do not commit node_modules. I cannot believe this still needs to be said, yet history tells me it does.
Monorepo vs multirepo
A multirepo architecture gives each application, service, or package its own repository.
Neither approach is automatically more mature. They optimize for different kinds of coordination.
Choose a monorepo when:
- Features regularly require changes across multiple projects.
- Apps share code, types, components, or configuration.
- You want atomic refactors and one place for engineering standards.
- Teams benefit from seeing how the whole system connects.
- You are willing to invest in repository tooling and boundaries.
Choose multiple repositories when:
- Projects are genuinely independent and rarely change together.
- Different teams need strong access or security separation.
- Each project has a completely separate lifecycle and technology stack.
- Sharing code is unusual rather than an everyday event.
- The repository would otherwise contain unrelated products that merely work for the same company.
My extremely scientific test is this: if one normal feature requires coordinated pull requests across three repositories, those repositories are already in a relationship. They are just refusing to label it.
A monorepo makes that relationship explicit.
Why I Keep Choosing Nx
Other tools can run and cache tasks in a monorepo, but Nx is the one I keep choosing because it helps me shape the entire workspace, not only make scripts faster.
I use Nx at work and for all of my personal projects. Over time, I have built a structure that lets me create applications very quickly because the code nearly every application needs already lives in reusable libraries.
Authentication, logging, monitoring, configuration, API clients, and shared UI all follow the same patterns across every app. A new application does not begin with rebuilding those foundations. I can start with pieces I already trust, compose them together, and focus on the problem that makes the new application unique.
Nx makes that structure easier to maintain. The project graph shows how everything connects, dependency rules protect boundaries, affected commands avoid unnecessary work, and generators turn repeated decisions into a reliable starting point.
This is one of the biggest practical benefits of a monorepo for me. Reuse is not limited to a utility function or a button. The standards themselves become reusable code. When I improve authentication or monitoring once, every application can benefit from the same improvement.
Rules for a Sane Monorepo
The folder structure matters less than the rules protecting it.
These are the ones I care about:
- Apps can depend on libraries; random libraries should not reach into apps.
- Shared code needs a clear owner and a real reason to be shared.
- Project boundaries should be enforced by tooling, not a document nobody has opened since onboarding.
- CI should run what changed, not punish the entire repository for one CSS edit.
- Releases should match how products actually ship.
- Every folder named
utilsmust explain itself before entering the building.
A healthy monorepo is not one enormous application. It is a neighborhood: multiple homes, shared roads, clear addresses, and one person who keeps leaving a broken chair beside the communal bins.
So, in defence of the monorepo
For products that share code and evolve together, yes. I will defend the monorepo.
You get atomic changes, easier refactors, consistent tooling, reusable code, and a much clearer picture of how the system fits together. The tradeoff is that you must take boundaries, build tooling, and repository hygiene seriously.
If your projects barely know each other, keep them separate. Do not force unrelated code into one repository just because somebody made a beautiful dependency-graph screenshot.
But if your “independent” repositories are constantly waiting for each other, publishing packages to each other, and coordinating releases through a group chat called URGENT, they may be asking for a shared home.
Give the monorepo a chance.
Worst case, you learn a lot about build graphs.
Best case, renaming one button goes back to being one pull request.