The Monorepo Hope Edition: One Year to Bring a Despairing Repo Back to Hope
Toss argues that a monorepo alone does not guarantee a consistent or efficient frontend development experience. The real problem was dependency-version fragmentation across services, which made installations slow, platform changes risky, and upgrades difficult. Toss addressed this by introducing shared dependency “catalogs,” standardizing core libraries while preserving controlled, gradual upgrades. ## Toss’s Frontend Development Environment - More than 100 frontend engineers maintain products inside and outside the Toss app. - Despite the large number of products, services use nearly identical versions of React 19, Next.js 15, TypeScript, bundlers, and linters. - A shared monorepo makes it easier to: - Maintain a consistent development environment. - Share code across services. - Propagate platform-wide changes. - Give users access to features such as React Concurrent Mode and Server Components. ## Problems with an Unmanaged Monorepo - Services used widely different dependency versions, including outdated React and supporting libraries. - This created fragmented developer experiences: - Some services had fast development servers and modern APIs. - Older services were slower and harder to develop. - Dependency installation could take more than a minute even with caching. - Platform teams struggled to test shared libraries across many React and library versions. - Service developers avoided upgrades because compatibility risks and migration costs were high. - Older services consequently became locked into outdated dependencies. ## Why Toss Rejected a Polyrepo Strategy - Splitting the monorepo into separate repositories could reduce the size of each individual project and improve installation times. - However, polyrepos would not solve the main issues: - Development environments would remain fragmented. - Shared-code development and updates would become more expensive. - Differences between services could become even more pronounced. - Toss concluded that improving dependency management within the monorepo was preferable to abandoning it. ## Simplifying the Dependency Tree - The central issue was that services selected different versions of the same core libraries. - Toss identified roughly 10–20 commonly used libraries, including: - React - Component libraries such as TDS - Jotai - TypeScript - ESLint - Standardizing these dependencies could: - Reduce installation time. - Provide a consistent developer experience. - Make platform-library testing more predictable. - Enable automated migration scripts and compatibility layers. - Lower the cost of adopting breaking changes. - In practice, developers usually chose libraries rather than requiring specific versions, making centralized versions practical. ## Dependency Catalogs - Toss defined recommended versions as a shared **Catalog** using pnpm or Yarn workspace configuration. - Services reference catalog-managed dependencies with the `catalog:` protocol instead of specifying independent versions. - Named catalogs can support different release channels, such as: - `stable` - `beta` - Toss initially included essential dependencies such as React, Next.js, TypeScript, TDS, and the Toss App SDK. - Catalog packages had to be tested in representative service environments before release. - New services automatically referenced the latest catalog. - CI detected cases where developers accidentally bypassed catalog versions. - Existing services were migrated collaboratively with their code owners. - Catalog changes were released as new versions and rolled out gradually rather than modifying a shared version in place. - Upgrade scripts and AI Skills reduced the effort required to migrate services. ## Results After Full Adoption - Dependency duplication fell substantially: - `.pnp.cjs` shrank from 96 MB to 15 MB, an approximately 84% reduction. - Development-server startup improved from 26.7 to 20.3 seconds, about 23% faster. - Full dependency installation decreased from 528.4 to 249.9 seconds, about 52% faster. - Developers gained greater confidence that catalog packages had already been tested in real services. - Centralized version control reduced incompatible transitive dependencies, such as one package requiring version 1 while a service used version 2. - Better dependency visibility made large architectural improvements safer, including work involving RSC, TypeScript 7, Rspack, and end-to-end testing. - Services could adopt improved platform packages more consistently and with less upgrade friction. The practical recommendation is to retain the monorepo, but enforce a curated set of shared dependency versions through catalogs, CI checks, staged releases, and automated migration tooling. This combines the sharing benefits of a monorepo with a more predictable and maintainable development environment.
Read original(opens in new tab)