Do you really need a monorepo?
For many teams, the honest answer is: probably not at first—but it depends heavily on why you think you want one.
Most teams arrive at the monorepo discussion because they're tired of duplicating code and types across services and frontends. The story is familiar: you start with a backend, add a frontend, then maybe a mobile app, and suddenly you're copy‑pasting types, request/response shapes, and utilities everywhere. You try to extract an npm package, discover that maintaining and versioning it is painful, and then start looking seriously at monorepo solutions like Turborepo or Nx.
This article is about interrogating that move: do you really need a monorepo, or do you just need better ways to share contracts and utilities?
A monorepo is a single repository that contains multiple apps, services, and libraries. Companies like Google and Meta made them famous, and tools like Turborepo and Nx have brought that model to everyday teams.
The typical motivations include:
variant="ordered"
items={[
{
title: "Shared types and utilities",
content: [
"Frontend and backend both need request/response types, validation schemas, and business logic helpers such as price calculations or date utilities.",
"Duplicating these across multiple repos is error‑prone and tedious, while a monorepo promises a single shared library used everywhere."
]
},
{
title: "Easier change management",
content: [
"When you change a type in the backend, you immediately see broken builds in your frontend or shared libraries in the same repo, which creates a tight and powerful feedback loop."
]
},
{
title: "Unified tooling and workflows",
content: [
"One CI configuration surface, shared linting and testing rules, and consistent TypeScript configuration."
]
},
{
title: "Local development quality of life",
content: [
"Developers can run multiple apps and packages from the same checkout with fast incremental builds and caching."
]
}
]}
/>
These are all real benefits—but they come with notable tradeoffs.
The Hidden Costs of Monorepos
Despite the appeal, monorepos are not free. Tools like Nx and Turborepo are powerful but introduce significant complexity and a learning curve. Nx in particular functions as a sophisticated build system and project graph manager, offering task graph analysis, incremental builds and caching, and a plugin ecosystem.
To really benefit from Nx, teams often need a solid understanding of TypeScript project references, path mappings and module resolution, and how build targets (build, test, lint) compose. For less experienced teams—or teams without strong TypeScript and tooling expertise—this can be a real barrier. The tool can feel almost magical until something breaks, at which point debugging becomes difficult. The breadth of Nx's capabilities, as visible in its documentation, hints at this complexity.
Over time, this can create a tightly coupled system where simple changes fan out widely, increasing both risk and cognitive load. Tooling and CI can become challenging as well: while monorepo tools promise faster builds, misconfiguration may lead to:
items={[
{
content: ["Longer CI times due to running too much too often"]
},
{
content: ["Complex caching strategies that are hard to reason about"]
},
{
content: ["Non-obvious dependency graphs"]
}
]}
/>
Without carefully tuned CI and development tooling, you can end up worse off than with multiple simpler repositories, undermining the original reasons for adopting a monorepo.
Before committing to a monorepo, many teams experiment with shared npm packages to avoid duplication. They extract common code into packages like @your-org/api-types or @your-org/utils, host them on a private registry such as GitHub Packages or a self‑hosted npm, and then consume these packages from both frontend and backend.
This approach solves some problems but introduces new ones. Versioning becomes an ongoing overhead: every change to a shared package requires bumping the version, publishing, and updating all consuming applications. That process can be tedious and disrupt development flow. There is also lag between changes and consumption; backends and frontends can drift out of sync if they don't upgrade package versions together, leading to subtle incompatibilities. Additionally, maintaining extra infrastructure and tooling for the packages requires effort: you need a private registry, authentication and authorization setup, and separate CI pipelines for the shared libraries themselves.
Over time, this friction is what often drives teams toward considering monorepos. They hope to replace the versioning and publishing dance with direct shared code and integrated builds. However, that suggests exploring alternatives such as code generation from an API specification before adopting a more complex monorepo architecture that may be unnecessary for the team's current scale and needs.
One of the core reasons teams reach for monorepos is the desire to avoid rewriting the same types and clients everywhere. Rather than manually maintaining a shared TypeScript library, another option is to generate types and client code from an API specification.
If your API is well described via OpenAPI or Swagger, you can generate types and client code for each consumer. Tools like Hey API take an OpenAPI spec and output TypeScript types and API client code for web, mobile, and other environments. In this model, the OpenAPI document becomes the contract between backend and frontend, replacing shared TypeScript code with a shared specification.
Benefits of API Specification-Driven Development
items={[
{
title: "Single source of truth",
content: ["The API spec becomes the single source of truth for request and response structures."]
},
{
title: "Separate repositories, consistent clients",
content: ["You can keep separate repositories for each project—web, mobile, backend—yet still generate consistent clients from the same spec, ensuring alignment."]
},
{
title: "Eliminate manual duplication",
content: ["Developers no longer hand‑write request/response types or clients for each app; the generator keeps them synchronized."]
}
]}
/>
This approach resembles other code generation tools like OpenAPI Generator or openapi-typescript, but modern tools such as Hey API focus on streamlining TypeScript-centric workflows.
By treating the API specification as a durable contract and automating client and type generation, teams can achieve many of the monorepo's benefits around shared contracts without the operational overhead and coupling that come from placing everything into a single repository.
When a Monorepo Makes Sense
There are clear situations where a monorepo is probably worth the added complexity. You should seriously consider a monorepo with Nx, Turborepo, or similar tools if you have multiple actively developed applications that heavily share code—for example, an admin panel, a public web app, several backend services, and a shared UI library. In such environments, code reuse is substantial and cross‑project coordination frequent.
Strong Tooling Expertise
A second indicator is having, or being able to develop, strong tooling expertise within your team. Someone needs to be comfortable owning TypeScript build configurations, Nx or Turbo task pipelines, and CI caching and optimization strategies. Without that ownership, the monorepo's complexity can quickly turn into fragility.
Unified Development Experience
A monorepo also shines if you want a unified development experience. Developers can rely on a single repository and a consistent set of commands, such as nx test or turbo test, and advanced features like nx affected:test to run only tests impacted by a change. This cohesion can make onboarding and day‑to‑day work smoother.
Atomic Cross-Project Changes
Finally, if you frequently need atomic, cross-project changes—such as updating a shared library and multiple services and frontends in one go—a monorepo becomes compelling. Having everything in one repo allows a single pull request and CI pipeline to validate the entire change set. For these scenarios, Nx is a strong choice, offering advanced features like distributed caching and detailed project graphs that help manage large, interdependent codebases more efficiently than a collection of loosely coordinated repositories.
In contrast, many teams can avoid monorepo complexity, at least initially. If you have a small team and only a few applications—perhaps one backend, one frontend, and maybe a mobile client—you may not gain enough benefit to justify the overhead. When your primary pain is simply duplicating types and API clients, adopting OpenAPI with code generation tools such as Hey API can often be sufficient.
Another factor is your capacity to manage tooling. If nobody on the team is interested in becoming the build tooling owner or digging into the intricacies of Nx or Turborepo, introducing heavy monorepo infrastructure may slow development instead of speeding it up. Similarly, if your deployment units are cleanly separated, with independent release cycles, simple CI setups, and relatively few cross‑project refactors, separate repositories can remain an effective structure.
As your system and organization evolve, you can revisit the monorepo decision when the scale and collaboration patterns begin to demand more integrated tooling and workflows.
A Practical Decision Framework
A practical decision framework can help you determine whether you truly need a monorepo. Begin by asking what problem you are actually trying to solve.
items={[
{
content: [
"If the answer is that you keep rewriting types and API clients, start by improving your API contracts using OpenAPI and generators like Hey API rather than immediately restructuring into a monorepo."
]
},
{
content: [
"If instead you need unified workflows, atomic cross-project changes, and extensive code sharing across many apps, a monorepo may be appropriate."
]
}
]}
/>
Assess Your Team's Tooling Capacity
Consider whether you have the time and interest to become comfortable with Nx or Turborepo and their TypeScript-related intricacies. Without that investment, adopting a monorepo might create more frustration than value.
A good rule is to try the least complex option that could work: tighten your API contracts, introduce code generation to reduce duplication, and standardize some tooling across repositories. If those measures fail to address scaling, coupling, or workflow challenges, then explore an Nx or Turborepo migration.
Do You Really Need a Monorepo?
You do not automatically need a monorepo just because you want to share types and utilities. Monorepos, especially when powered by tools like Nx, are powerful architectures for complex, multi-app systems, but they impose a real learning curve and operational overhead.
If your primary pain centers on duplicated types and API clients, tools such as Hey API that generate TypeScript types and clients from OpenAPI or Swagger specs can provide consistency and shared contracts without consolidating everything into a single repository.
Top comments (0)