5 Ways to Build a React Monorepo

Best tools to build a production-grade React monorepo: From fast builds to code-sharing and dependencies.

Jonathan Saring
Bits and Pieces

--

The need for a monorepo architecture that supports the development of a React application system is greater today than ever before.

When teams build several different applications, dozens of “micro-frontends” aka features and experiences, and hundreds of smaller components — the traditional repo architecture won’t do.

Problems like code-sharing begin to rise, as different applications need to consume and use the same components, features, or even each other.

Standardization for tools, techs, and versions becomes a serious issue, as does the management of dependencies in your system. Even the way you build your applications and run common dev-tasks needs to be refigured.

Naturally, where there’s a need — nature provides. Or in this case, the open-source community provides us with some really great tools to solve this.

From next-generation tools like Bit that let you build and manage any number of apps, features and components in one workspace with very little effort and infinite code-sharing by design, to super-smart build tools like NX, there are many exciting options to discover.

In the below list I’ve rounded up my top 5 choices for a ReactJS monorepo architecture, plus one bonus tool from the past that you should never use.

Enjoy, and please feel free to comment and suggest more tools or share from your experience. Happy reading 🍻

Also read:

1. Bit

Bit is a next-gen tool for a monorepo with React apps and components.

In short, it solves the key problems of a monorepo:

  • Configurations on the workspace and component level
  • Code sharing on the next level via composability
  • Standardization of code, tech, and dev tasks
  • Ownership over components / features / experiences
  • Build, test, and deploy changes on a graph
  • Auto-defining and managing dependencies at app/component level
  • Dev experience that stays simple at scale
Bit’s website is just another React app in the codebase

With just a few CLI commands you can setup an entire monorepo with React apps, a component library, packages, and super-performant build and test pipelines that work with just about any tool possible for testing etc.

$ bit new react my-workspace$ bit create react-app apps/my-react-app1 component(s) were created
my-name.my-scope/apps/my-react-app
location: my-scope/apps/my-react-app
env: teambit.harmony/<aspect> (set by template)
$ bit run apps/my-react-appapps/my-react-app app is running on http://localhost:3000

What stands Bit apart from other monorepo tools is the modularity and composability. With Bit Apps, pages, features, backend modules, and UI elements are all “components”. In the Bit workspace you create and compose components, apps, and projects with a simple and rich dev experience.

Yes, an app is a component in the workspace too.

Components are not coupled to a workspace; you can dynamically create, fetch, and export components from a workspace and only work on what you need in a small and simple codebase. Each component is like a “mini-project” and can be developed, versioned, published, tested etc independently.

Bit smoothly handles and automates things like packages and depndencies to create a very simple and scalable dev experience even for a lrge monorepo. Features like component dev-environments and templates help you easily define and manage configs for many components and applications.

Bit plays nativly with TypeScript and almost any tech in the modern web ecosystem for things like testing or linting and even works with React Native. You can rather wuickly extend it to add new tools and workflows.

You can try it out here with a template.

For every app and component, Bit automatically defines and manages dependencies, package dependencies, and dev / peer depdencies. Problems like gosht depndencies and clashing versions will no longer exist. When you update a component, Bit “knows” which other components should be built too, and will prompt you to run a command to test, build and update them each in isolation — so builds become faster, paraller, and safer than ever.

Watch the creator of pnpm explain monorepo dependencies with bit

Bit also solves two more major problem of a monorepo: discoverability and collaboartion. Every component can be hosted in a scope (e.g. “design” “billing” “search” “base-ui” etc) which every developer can access, search, explore, visually play with, and install components from.

Getting started → Install Bit and create a workspace:

2. NX

NX is a powerful build tool for React monorepos.

It automates tasks that developers must repeat manually and includes features like computation caching, incremental builds, build automation, and it also includes a plugin integration with Cypress.

Like Bit NX Creates a workspace, which contains the monorepo setup for your project of many projects.

myorg/
├── apps/
│ ├── todos/
│ │ ├── src/
│ │ │ ├── app/
│ │ │ ├── assets/
│ │ │ ├── environments/
│ │ │ ├── favicon.ico
│ │ │ ├── index.html
│ │ │ ├── main.tsx
│ │ │ ├── polyfills.ts
│ │ │ └── styles.css
│ │ ├── .babelrc
│ │ ├── .browserslistrc
│ │ ├── .eslintrc.json
│ │ ├── jest.config.ts
│ │ ├── project.json
│ │ ├── tsconfig.app.json
│ │ ├── tsconfig.json
│ │ └── tsconfig.spec.json
│ └── todos-e2e/
│ ├── src/
│ │ ├── fixtures/
│ │ │ └── example.json
│ │ ├── e2e/
│ │ │ └── app.cy.ts
│ │ └── support/
│ │ ├── app.po.ts
│ │ ├── commands.ts
│ │ └── e2e.ts
│ ├── .eslintrc.json
│ ├── cypress.config.ts
│ ├── project.json
│ └── tsconfig.json
├── libs/
├── tools/
├── .eslintrc.json
├── .prettierrc
├── babel.config.json
├── jest.config.ts
├── jest.preset.js
├── nx.json
├── package.json
├── README.md
├── tsconfig.base.json
└── workspace.json

NX creates a graph of the dependencies between all the projects in the repository. Exploring this graph visually can help get a high level view of your code architecture. Unlike Bit, the graph does not visualize or indicate the relationship between the different components in the workspace.

As a build tool, NX prides on efficient incremental project builds. It will rebuild what is necessary and to never run the same computation twice, to reduce build time for your projects. It does so impressivly.

Like with Bit, Nx plugins also help you develop React applications with integrated support for tools and libraries like Jest, Cypress, ESLint, and more. Nx also supports frameworks like Next.js, and has support for React Native.

3. Lerna

Lerna is now maintained by the NX team, but was originally created by the developers of BabelJS. It was built as a rather simple tool for managing repositories with multiple packages, so you can version and publish each package separately. It provides useful automation for that purpose.

packages/
header/
src/
...
package.json
rollup.config.json
jest.config.js

footer/
src/
...
package.json
rollup.config.json
jest.config.js

remixapp/
app/
...
public/
package.json
remix.config.js

package.json

You can keep all packages on the same version, and the version is kept in the lerna.json file at the root of your project. Or, split versioning between them. Then you can later publish one or all packages from the repo.

Lerna does take an effort to help reduce build times. Lerna will not run tasks executed before, and instead will restore the files from its cache. Computation cache can be distributed to several machines, to further reduce build time.

For React, Lerna might come a bit short; You want to develop several apps and libraries, components, experiences, share code between them and compose them together, and scale this workflow. Since Lerna was built for the resolution of “package=project” and due to its set of features, it will still require a great number of configs and have gaps when it comes to defining dev tasks for components, updating changes, or handling dependencies.

4. Turborepo

Turborepo was recently acquired by Vercel. It offers a performant build system for JS and TS monorepos. As such, it matches the standard for incremental builds that utilize smart and efficient computation and caching.

Turborepo is somewhat of a hybrid between NX and Lerna, in the way that it focuses on packages yet provides similar tools to NX in the workspace such as incremental builds, parallel execution, remote caching, and task pipelines.

Benchmarking NX and Turborepo (source: in link)

Here’s a nice benchmarking project for nx and turborepo on GitHub:

5. Rush

Rush is a monorepo setup tool that originated in Microsoft as a tool for managing repos with many packages, and has a powerful incremental and super efficient build system with incremental build and smart computation, but it sometimes falls short on dev experience, ecosystem compatibility, and the whole notion of managing your codebase on a graph like Bit.

git clone https://github.com/microsoft/rushstack
cd rushstack

git clone https://github.com/microsoft/rushstack
cd rushstack

# Install the NPM packages:
# (If you don't have a GitHub email configured, add the "--bypass-policy" option.)
rush update

# Incremental install:
rush update # <-- instantaneous!

# Force all projects to be rebuilt:
rush rebuild

# Incremental build:
rush build # <-- instantaneous!

# Use "--verbose" to view the console logs for each project as it is built.
# Projects build in parallel processes, but their logs are collated.
rush rebuild --verbose

There’s a wonderful post by spencer eliiot that compares Rush with NX, so I’ll spare this part and you can learn more by heading over there:

Here’s a nice implementation guide with a real example for a Rush+React monorepo, and here’s an example project on GitHub.

⭐️ Bonus: Git submodules (don’t…)

So true

If you run a quick Google search for Git submodules, the results will not be positive. This is because of some major drawbacks around git submodules, such as being locked to a specific version of the outer repo, the lacking of effective merge management, and the general notion that the Git repository itself doesn’t really know it’s now a multi-module repository.

Git, at its base, also isn’t built to handle dependencies and relationships between components. The workflow around code-sharing, therefore, becomes complicated, and Submodules are struggling to deliver our desired workflow. In mercurial, subrepositories are named “feature of last resort” to be avoided.

So yeah, don’t use it unless all else fails.

--

--

I write code and words · Component-driven Software · Micro Frontends · Design Systems · Pizza 🍕 Building open source @ bit.dev