Monorepo for large-scale React applications
When working on real-world enterprise solutions, it's common for software products to adapt to changes regularly using agile software development methodologies like Scrum. As a result, it becomes increasingly important for software developers to figure out how to create reusable components, share common building blocks, and develop with flexibility. With good practices, it can make development easier for the whole team and allow for better iteration of products.
For example, we will likely need to share our company logo and themes throughout all of our apps. To do this, we need a sharable design system and components so we can focus on business logic, but we also don't want to have to manually copy and paste changes when making updates. This is where the concept of a monorepo comes in.
A monorepo is a repository where developers put different services or tools that share similar configurations or common logic. In our case, we are building various SaaS products mainly using
Monorepo framework candidates
We started with
lerna v4 with
yarn v1 workspace. Switching to
npm v7 workspace, yarn v2 workspace (tried PnP) with custom scripts, then finally settled with
lerna v6 (powered by
Package managers with workspace protocol
|yarn v1 workspace||Easy to use, interactive upgrade is helpful||Inappropriate hoisting leads to large |
|npm v7 workspace||Out-of-box module resolution, dependencies pruning scripts||Lacks flexible workspace scripts|
|yarn v2 workspace PnP||Small package size (no ||complex set-up (esp. for installing libraries that don't support PnP), limited IDE supports, no flexible workspace scripts|
|pnpm workspace||Hard-linked ||Limited CI support|
I really like the concept of
Yarn PnP, especially the way it eliminates the node_modules directory. However, it can be quite difficult to set up, requiring a lot of package patching and debugging of installed libraries, as well as workarounds to configure IDEs with developer tools like
This is where pnpm comes in. The more we've used it, the more we've grown to love it. It's easy to apply the
--filter flag to build custom npm scripts.
Another game changer is the
Next.js standalone mode, which copies the required node_modules dependencies to a standalone folder for lean Docker deployment. This helps to solve the problem of large Docker image sizes caused by unnecessary dependencies, particularly
Version management tools
With lerna v4, we used the
lerna version scripts and adopted conventional commits and conventional changelog. However, it broke when we switched to
yarn v2 due to the new workspace protocol and lerna was not maintained for a long time. We had to find an alternative. In the end, we compromised by using
standard-version with workspace scripts to generate the changelog recursively.
The future looks brighter now that the
nx team has taken over maintenance of
lerna and replaced the underlying logic with
nx's existing engines.
Workspace management and build
As the monorepo grows with more components and internal package dependencies, we've looked into other management tools to improve the CI process and Docker build scripts with cache. When
lerna v4 was deprecated, we tried using
yarn PnP to compile and bundle all dependencies like a binary file. This increased our storage usage and required us to commit the compiled files to the
git repository so the CI server could pick them up and continue the process. This isn't ideal when we add more and more packages.
We've also experimented with new build tools like
turbo, comparing it to
nx provides detailed documentation with years of development and use cases, and the
nx cloud service makes it easy to set up free remote caching with our current team size and CI tools.
Turbo offers similar features with easier integration with
There are many monorepo tools out there, and our experiences may not be entirely accurate or correct. We encourage you to try out these tools and share your thoughts as well!