Multiple teams, one Next.js app, and nobody who truly owned it. Teams would jump in, make their change, and leave. No cleanup, no consistency — just ship and move on to the next ticket. Different PMs, different roadmaps, same neglected repository. The code reflected exactly how much anyone cared about it.
The solution landed on a monorepo. Turborepo, multiple Next.js apps, shared packages for the design system and common utilities, TypeScript everywhere. The logic was simple: give each team their own app and let them own their code. CloudFront up front, an ALB routing traffic to the right app based on path with two months of migration work.
I knew from the beginning it wasn't quite the right solution. The problem was organisational, not technical. But the business had no appetite for restructuring teams, and the pain was real and immediate. So we solved what we could with the tools we were allowed to use.
We didn't have a codebase problem. We had multiple teams owning something that should have had one owner.
What It Solved
Credit where it's due: the monorepo did what it was asked to do. Teams stopped blocking each other on deploys. Engineers started taking ownership. Each app had its own CI pipeline, its own build config, its own release cadence. The merge conflicts dropped. The "who broke main?" Slack messages slowed down. If the goal was "stop three teams from colliding in one repo," mission accomplished.
For a while, it felt like the right move. People were happier. Deploys were calmer. We'd taken a real source of pain and addressed it with a real piece of engineering. I was proud of the work.
What It Cost
The first thing you notice when you split a single Next.js app into multiple apps behind an ALB is that your website doesn't feel like a website anymore. Click a link that crosses an app boundary and you get a full page reload. The shell re-renders. The JavaScript re-initializes. State is gone. Users don't care about your org chart — they just know the site feels broken. That's a UX sacrifice made entirely because of how teams were structured, not because of any technical requirement.
Then there was the dependency management. Hoisting issues that turned a five-minute PR into a forty-minute archaeology expedition through node_modules. The build tooling became its own area of expertise — one that nobody had signed up to maintain full-time but everyone depended on.
We traded one set of coordination problems for another and called it progress.
And here's the part that should have been the warning sign: the meetings didn't stop. The whole point was to give teams independence, but they still shared a design system, a utils package, a types package, and a handful of other common dependencies. Every change to a shared package required cross-team coordination. Interface changes needed meetings. We'd drawn boundaries through the code but the code didn't care — it was still coupled, and now coordinating that coupling required a myriad of cross-team syncs instead of a quick conversation in a single team's standup.
We traded one set of coordination problems for another and called it progress.
The Real Problem
Melvin Conway figured this out in 1967: organizations produce systems that mirror their communication structures. Many teams, multiple apps. The architecture wasn't just influenced by the org chart — it was a direct encoding of it. And we'd spent two months making that encoding official.
The thing is, the website wasn't big enough to justify three teams. It wasn't three products stitched together. It was one product with three groups of people working on it. The contention wasn't a sign that the codebase needed to be split. It was a sign that the ownership model was wrong. We didn't have a codebase problem. We had three teams owning something that should have had one owner.
No amount of Turborepo configuration was going to fix that.
The Model I'd Use Now
One web team. One repo. One application. Product verticals bring requests to the web team the same way they'd bring requests to any platform team.
When a product vertical has a big initiative — a new feature area, a major redesign, a complex integration — a web engineer embeds with that team. Internally forward-deployed engineers, if you want a name for it. They sit in the product team's standups, absorb the context, understand the domain. Then they bring that context back to the web team and build it in a codebase they know deeply. The product team gets velocity. The codebase stays coherent. The UX stays seamless.
The web team becomes a quality gate, not a bottleneck. They own the standards, the architecture, the shared patterns. Product teams get dedicated attention without getting direct commit access to infrastructure they don't fully understand. And nobody needs a cross-team meeting to align on shared code because there is one team. Org problem, org solution.
The monorepo wasn't a bad tool. It was the right answer to the wrong question.
The Takeaway
The monorepo wasn't a bad tool. Turborepo is good software. The migration was well-executed. The architecture was sound for the constraints it was designed around. But those constraints were symptoms, not root causes. I knew that going in — but when the business won't restructure teams, you work with what you've got.
When you find yourself reaching for architecture to solve a people problem, pause. Look at the org chart. Look at the communication lines. Ask whether the friction you're feeling is really about code, or about who owns what and how they talk to each other. Because you can refactor a codebase in two months. Refactoring an org takes a difficult conversation and six months of convincing the business it matters.
The org chart always wins.