Here's what actually happens when your startup grows beyond 50 developers.
When Everything Breaks at Once
I watched a team spend 8 months migrating to Nx 15.8. Their builds went from 20 minutes to 3 minutes, which sounds great until you realize that 3-minute builds turn into 30-minute builds once you have enough interdependencies. The real kicker? When the build breaks, it breaks for everyone. All 200 developers just stop working until someone figures out why the TypeScript compiler ran out of heap space with error TS2556.
Microservices aren't better. At my last company, we had something like 50 services for what could have been one application. Want to test a feature end-to-end? Good luck spinning up 12 different services locally. Oh, and service A depends on version 2.1.3 of the auth library, but service B needs 2.2.0, and they're not compatible. Have fun with that dependency hell.
The Tools: Pick Your Poison
Nx is like having a really smart roommate who organizes everything perfectly but judges your life choices. It works great until you need to do something slightly different from what the Nx team anticipated. Then you're reading 400-page configuration docs at 2 AM wondering why your "simple" React app needs 17 different configuration files.
The learning curve is fucking brutal. I've seen senior engineers take 3 weeks just to figure out why nx generate @nrwl/react:lib shared-utils
fails with "Cannot find module '@nrwl/devkit'". The plugin ecosystem is nice when it works, but debugging generator failures will make you question your career choices.
Turborepo is what Nx should be - simple and fast. Installation actually works out of the box, which is shocking in the JavaScript ecosystem. The caching is solid and the configuration is manageable. The downside? It's basically JavaScript-only. Got Python microservices? Go fuck yourself.
Also, Vercel owns it now, so expect monetization to creep in. The remote caching is already a paid feature, and it's only a matter of time before more stuff goes behind the paywall.
Bazel can handle any language and any scale, but using it requires a PhD in build systems. I watched a team of maybe 20 engineers spend 6 months migrating to Bazel 6.2. It worked perfectly... until someone needed to add lodash as a dependency. That took 2 days of BUILD file archaeology because apparently rules_nodejs
doesn't play nice with npm_install
.
The documentation assumes you're Google. The error messages are written by people who hate other people. And if you think you're going to migrate off Bazel easily, think again. It's like Hotel California but for build systems.
Microservices: Death by a Thousand Cuts
The alternative is splitting everything into microservices, which trades one big problem for many small problems.
Each service needs its own CI/CD pipeline. That's 50 different ways your deploy can fail. Each service needs monitoring, logging, and alerting. Your operational overhead just went from "manageable" to "requires dedicated teams."
Want to rename a field in the User model? Hope you enjoy coordinating releases across 8 different teams with different sprint schedules. What used to be a 10-line find-and-replace is now a 3-month cross-team initiative with feature flags and backward compatibility shims.
But hey, when service A shits the bed, services B through Z keep working. That's something.
The Reality: Everyone Compromises
Most places end up with a hybrid approach because pure strategies are for people who don't have to maintain this garbage in production. Core platform stuff goes in the monorepo because you need atomic changes. Product features get their own repos because teams want autonomy.
The result? You get the complexity of both approaches and the benefits of neither. But at least it kind of works, and "kind of works" beats perfect architecture that never ships.
There's no right answer here, just trade-offs you can live with. Pick the one that makes you want to quit less and focus on problems your customers actually give a damn about.