# Monorepo vs Polyrepo: The Stack Choice That Sticks

URL: https://whatshouldibuildnext.com/journal/monorepo-vs-polyrepo
Type: blog
Locale: en
Published: 2026-09-26
Updated: 2026-09-26

---

> Should you use a monorepo or polyrepo? It depends on whether services share code, who needs access, and your CI tolerance. Here's the practical framework.

The monorepo vs polyrepo question comes up before every new project, and the answer shapes months of CI/CD setup, refactoring overhead, and team access control. For most solo devs building a product with shared code, the right default in 2026 is monorepo. Not because it's the trendy choice, but because it removes the publishing ceremony that polyrepos impose the moment two packages need to talk to each other. If your services are fully independent and will genuinely never share code, polyrepo is simpler. Every other case is context.

It's 22h. You've got a new project taking shape: a backend API, a package of shared TypeScript types, and a frontend dashboard that will inevitably consume both. Two tabs open. The cursor is blinking.

Most posts on this topic are written for engineering teams of twenty with a dedicated DevOps engineer who enjoys configuring Bazel. This one is for builders who need to make the call before the first commit, because restructuring six months in, when you have real users and a CI pipeline your muscle memory has memorized, is the kind of thing that kills a side project's momentum for good.

## What a monorepo actually buys you (not the textbook version)

The standard pitch is "one repo, shared dependencies, atomic changes." That's all real. But the part that actually matters day-to-day for a solo builder is more concrete: you don't have to publish packages to use them locally.

In a polyrepo setup, if `shared-utils` needs a fix that also affects `api-service`, you either publish a new version of `shared-utils`, bump the dependency in `api-service`, wait for CI to pass, and then deploy. Otherwise you fall back on `npm link` hacks that work until they stop working mid-sprint. In a monorepo with workspaces, you change the code, and every package that imports it sees the change immediately. No publishing ceremony.

Here's what that looks like with pnpm workspaces, which adds minimal configuration overhead:

`/packages
  /shared-types      ← imported directly by api and web
  /api
  /web
package.json         ← workspace root with "workspaces" field`
```
`// api/package.json
{
  "dependencies": {
    "@myapp/shared-types": "workspace:*"
  }
}`
```
No publishing. No version bumping during development. `workspace:*` resolves to the local package, and TypeScript's project references give you incremental compilation across the whole graph.

The second real benefit: cross-package refactoring that lands in one PR. Rename an interface in `shared-types`? TypeScript tells you every consumer that broke, in the same codebase, in the same editor session. In a polyrepo, you rename it in repo A, publish a new version, and discover the breakage in repo B three days later when a colleague runs `npm install` and the types don't match the runtime anymore.

The third benefit, which solo devs underestimate: one place for tooling configuration. One `.eslintrc`, one `prettier.config.js`, one CI workflow file. Small savings per change, but they compound over months of iteration.

It's worth naming what a monorepo does not give you: it does not make services less coupled. If your services are genuinely independent and you put them in a monorepo anyway, you've added coordination overhead without a return on it. The monorepo's benefits materialize only when the services actually share code or need to change together. The repo structure should reflect the dependency structure, not impose one.

![Abstract visualization comparing monorepo single tree versus multiple polyrepo boxes](https://fdzlnqpwsaniezitwiuw.supabase.co/storage/v1/object/public/cms-media/whatshouldibuildnext/2026-09/06d7c6-img-1.webp)

## When polyrepo earns its place

Polyrepo is not a mistake. It's the right choice for specific situations, and pretending otherwise is how you end up with a 40-service monorepo that takes 25 minutes to clone.

The clearest case: services with genuinely different ownership, release cycles, or compliance requirements. If your billing service is PCI-scoped and your marketing site is not, keeping them in separate repositories means keeping access control, audit logs, and blast radius cleanly separated. That's not operational overhead. That's the feature.

Polyrepo also wins when you're open-sourcing part of your codebase. A dedicated public repo lets external contributors fork and PR without pulling your private infrastructure into scope. GitHub's repository-level permission model doesn't give you clean path-based access isolation at scale. A separate repo handles that correctly.

And for pure microservices with completely distinct tech stacks: a Go service and a React Native app that literally share nothing at the code level, a monorepo adds coordination cost without delivering the benefit. If there are no shared packages, there's nothing to share.

The honest version of this: most indie projects that start with a polyrepo end up regretting it, not because polyrepo is bad architecture, but because they overestimated how independent the parts would stay. The frontend always ends up needing a type from the backend. The worker always ends up needing a utility from the API. Two repos become four PRs for every feature.

## The CI/CD cost that nobody mentions until it hits your bill

Monorepos have a real operational cost: if your CI naively runs everything on every commit, you're paying in time and money for builds and tests that have nothing to do with what you changed.

Push a CSS tweak to the web package. CI runs the full test suite for `api`, `worker`, and `shared-types`. That's six minutes of GitHub Actions compute for a color change. At scale, this turns into CI queues that block your whole team.

The solution exists, but it requires deliberate setup: build orchestration tools that understand your dependency graph. [Turborepo](https://turbo.build/) and Nx both solve this. Turborepo's `turbo.json` defines a pipeline where each task runs only for packages with changed inputs:

`{
  "pipeline": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": ["dist/**"]
    },
    "test": {
      "dependsOn": ["build"],
      "cache": true
    }
  }
}`With remote caching enabled (free on Vercel's Turborepo Cloud tier for small teams), a cache hit on an unchanged package is instant, no rebuild, no retest. For a solo dev on a side project, this keeps CI under two minutes on most pushes once the build artifacts are warm.

The cost: you need to learn Turborepo or Nx before you need it, not after. It's roughly half a day of setup. If you skip it and let CI accumulate fat, monorepo CI will slow to the point where you start questioning the whole architectural choice, and that's usually when developers decide polyrepo was right all along, when the real problem was just a missing config file.

![CI/CD pipeline dashboard showing parallel build jobs and deployment status for monorepo](https://fdzlnqpwsaniezitwiuw.supabase.co/storage/v1/object/public/cms-media/whatshouldibuildnext/2026-09/10ba32-img-2.webp)

## How AI coding tools changed the math on this debate

Until recently, one genuine argument for polyrepo was cognitive load: smaller repos are easier to reason about because they're isolated. Context-switch to a new service, see only what's relevant for that service. The argument was legit.

AI coding tools shift that calculation. When you're working with Cursor, Claude Code, or GitHub Copilot, the tool works with your full codebase in context. It sees cross-service dependencies, understands which interface is consumed by which service, and can track a renamed field through every consumer without you holding the mental model yourself. The "isolated repo is easier to understand" argument weakens significantly when your AI assistant holds the whole dependency graph in context anyway.

A concrete example: in a polyrepo setup, if you ask an AI assistant to refactor an API endpoint that also affects a shared type, it typically can't see both repos in one session. You're doing the coordination manually, which is exactly the overhead a monorepo was supposed to eliminate. In a monorepo, the same refactor is one conversation.

This doesn't flip the decision entirely. But it removes one of the historical justifications for polyrepo for small teams and solo devs, and tips the default slightly further toward monorepo when the code is genuinely coupled.

## Three signals that mean you should split the repo now

You started with a monorepo. Good. But here are the concrete signals that the split has become the right call:

**Access control is becoming load-bearing.** A contractor needs frontend access, not backend. A partner integration team needs to read your API schema but nothing proprietary. GitHub's Codeowners can restrict who can review which paths, but it doesn't restrict read access. If read isolation matters for compliance or security, separate repos are the clean answer. Codeowners gymnastics never fully replaces repository-level permissions.

**CI failures in one service are blocking deployment in an unrelated one.** If a broken test in `payment-service` is gating a hotfix you need to ship in `marketing-site`, your monorepo is creating coupling that your codebase doesn't have. Either the build configuration needs fixing (task scoping with Turborepo will solve this), or the two services genuinely don't belong in the same repo.

**One part is going open-source and one isn't.** This is the cleanest split case. Extract the open-source piece into its own public repo. Mixed public/private code in a GitHub monorepo is genuinely painful: you'd need a separate organization or a manual pruning process that creates permanent maintenance overhead.

## The practical setup most builders land on

The answer most experienced developers settle on: one monorepo per product domain, not "one monorepo for everything you've ever built" and not "one repo per package."

If you're building a SaaS with a web frontend, an API, and a shared type library, that's one product. Keep it in one monorepo. If you also maintain an open-source CLI utility that other projects use, that's a different repo with a different audience and release cycle.

The mistake is treating the monorepo/polyrepo choice as ideological. It's not "monorepo teams" versus "polyrepo teams." It's a structural decision based on three questions: What is the dependency graph between your services? Who needs access to what, and does isolation matter? What is your tolerance for CI/CD setup complexity?

![Solo developer working on laptop considering project architecture decisions](https://fdzlnqpwsaniezitwiuw.supabase.co/storage/v1/object/public/cms-media/whatshouldibuildnext/2026-09/0b60e1-img-3.webp)

For side projects specifically: start with a monorepo using pnpm workspaces (npm workspaces work too, just less ergonomic). Add Turborepo only when your CI starts regularly hitting 4+ minutes. Split into a separate repo only when you have a concrete reason: open-source, access control, compliance, or a partner team with different release cadence. Not because it feels architecturally cleaner.

The monorepo vs polyrepo debate is one of those choices where the right answer for most solo devs is the same: start simple, optimize when the specific friction appears. The cursor is still blinking. Ship the first commit.

## FAQ

### Is a monorepo better for AI coding tools like Cursor or GitHub Copilot?

Yes, generally. AI coding assistants work best when they can see the full dependency graph in one context window. In a monorepo, a tool like Cursor can trace a change from a shared type through every consumer in one session. In a polyrepo, that cross-repo context is missing or requires manual setup, putting the coordination back on you.

### What's the actual difference between Turborepo and Nx?

Both are monorepo build orchestration tools that skip builds for unchanged packages using dependency graph analysis. Turborepo is simpler to configure and works well for JavaScript/TypeScript projects. Nx is more feature-rich with built-in generators, a project graph UI, and support for multiple languages. For a side project, Turborepo is usually the right starting point.

### Can I migrate from polyrepo to monorepo later?

Yes, but it's disruptive enough that you'll want to do it deliberately. The process involves moving each package into a workspace structure, updating imports, adjusting CI pipelines, and optionally rewriting git history using git subtree or git-filter-repo. Most teams who've done it say it was worth it, but budget two to three days minimum for a project of any meaningful size.

### Do big companies use monorepos?

Google, Meta, Microsoft, and Twitter have all historically used monorepos for their main codebases. Uber's iOS and Android teams both switched to monorepos specifically to reduce cross-service coordination overhead. That said, the tools those companies use (Bazel, Buck, Pants) are far more complex than what a solo dev needs. Turborepo and pnpm workspaces cover 95% of the benefits without the infrastructure overhead.

### Does a monorepo affect how I deploy to Vercel or other platforms?

Most modern platforms handle monorepos natively. Vercel lets you specify a root directory per project, so you can deploy packages/web while pointing the build command at the monorepo root. Netlify and Railway have similar support. The configuration takes about ten minutes and does not require any special infrastructure beyond what you already have.

### Should a solo dev set up Turborepo from day one?

Not necessarily. Start with pnpm workspaces for the shared package benefits. Add Turborepo when your CI starts consistently taking more than three to four minutes, or when you want remote caching to speed up local builds. Adding Turborepo to an existing workspace setup takes roughly thirty minutes, so you are not locking yourself in by waiting.