We use monorepos for several of our projects - including our AI RAG application. When you have a unified codebase where frontend, backend, and shared components live together, it's significantly easier to work across the stack.
But monorepos aren't for everyone. Here's our practical guide to when it makes sense - and when you should stick to separate repos.
What is a monorepo?
A monorepo is one repository containing multiple projects. Instead of:
company/frontend-repo
company/backend-repo
company/shared-components
company/mobile-appYou have:
company/monorepo
├── apps/frontend
├── apps/backend
├── apps/mobile
└── packages/shared-componentsTools like Turborepo, Nx, and Lerna help manage builds, caching, and dependencies across projects.
When monorepos make sense
1. You have 3+ apps sharing significant code
If your frontend, backend, and mobile app all use the same TypeScript types, UI components, and utility functions - then a monorepo makes sense.
Keyword: significant code. If you share 2-3 small utility functions, it's not enough.
2. You have separate teams working on related projects
Monorepos make it easier to:
- Make atomic commits across projects
- Update shared dependencies once
- See the entire system in one IDE
3. You need consistent tooling
Same linting rules, same test setup, same CI/CD pipeline for all projects.
Real-world example: Our AI platform
We use Turborepo for an enterprise document search platform we're building. The structure looks like this:
project/
├── apps/
│ ├── web/ # Next.js frontend
│ └── worker/ # BullMQ background jobs
├── packages/
│ ├── db/ # Database client and schema
│ ├── documents/ # Chunking and text processing
│ └── shared/ # Types and validation
└── turbo.jsonWhy it works for us:
- One TypeScript codebase - Types are shared automatically between frontend and worker
- Shared packages -
packages/documentsis used by both web and worker - Parallel development -
bun run devstarts everything at once - Cached builds - Turborepo only builds what changed
When it's overkill
1. You have one product
If you're building one webapp with one backend - why split it up just to put it back together?
A simple Next.js app with API routes is fine. You don't need "apps/web" and "packages/ui" and "packages/config".
2. Your team is under 5 developers
Monorepos solve coordination problems between teams. If you're sitting next to each other, you can just talk.
3. You're starting a new project
Monorepos add complexity from day one:
- More complicated CI/CD setup
- Longer build times (without caching)
- More configuration to maintain
Start simple. Migrate to monorepo when you have the problem, not before.
The alternatives
npm workspaces
If you just want to share code between 2 projects, npm workspaces are often enough:
{
"workspaces": ["packages/*", "apps/*"]
}No Turborepo, no Nx - just native npm.
Git submodules
Works technically for read-only dependencies - but rarely a good developer experience. Versioning and CI integration quickly becomes cumbersome.
Package registry
Publish your shared packages to npm (or a private registry). More overhead, but clearer boundaries.
Why we use Turborepo
Turborepo has evolved massively. With v2.7+, it's become a serious tool:
Devtools and visualization
Turborepo's new Devtools let you visually explore your Package Graph and Task Graph. They hot-reload when you change code - fantastic for debugging cache issues.
Intelligent caching
Remote caching via Vercel means builds are shared across your team. One developer builds, everyone else gets a cache hit. On larger projects, this saves hours every day.
Composable configuration
With extends, packages can inherit configuration from each other. No more copy-pasting tsconfig and eslint rules.
Yarn/npm/pnpm/Bun support
Works with all package managers. We use Bun - it works perfectly.
Turborepo vs Nx
Both have evolved significantly in 2025. Here's an honest comparison:
| Feature | Turborepo | Nx |
|---|---|---|
| Setup time | 5 minutes | 15-30 minutes |
| Remote caching | Free (Vercel) | Paid (Nx Cloud) |
| Graph visualization | Devtools | Nx Graph (redesigned) |
| Package managers | All | All |
| Enterprise features | Basic | Conformance, Polygraph |
| AI integration | None | Cursor/Copilot config |
| Polyglot (Java, .NET) | JS/TS only | Maven, Gradle, .NET |
| Rust-powered core | No | Partially |
Our choice: Turborepo
We use Turborepo because:
- Simplicity - Less configuration, faster start
- Free caching - Vercel integration without extra costs
- Perfect for JS/TS - Our primary stack
When Nx is better
- Enterprise monorepos with 50+ projects
- Polyglot workspaces (Java, .NET, Gradle)
- Advanced CI with self-healing and conformance
- AI-assisted development with built-in agent configuration
The hidden costs
1. CI/CD complexity
You now need to handle:
- Which projects should be built for which changes?
- Build caching
- Test parallelization
2. IDE performance
Large monorepos can slow down your IDE. TypeScript language server has to index much more code.
3. Onboarding
New developers need to understand the entire monorepo structure before they can make their first PR.
Conclusion
Monorepos are a powerful tool - for those who need them. Most don't.
Ask yourself:
- Am I really sharing that much code between projects?
- Do I have coordination problems between teams?
- Is my current structure actually a problem?
If the answer is no, keep your separate repos. It's not old-fashioned - it's pragmatic.