Monorepo for React Native: When to Use, When Not to Use

Ramanpreet Singh
SquadStack Engineering
6 min readNov 8, 2022

By Ramanpreet Singh

Introduction

It’s been a year since we started to rewrite our Android app in React Native, and during this period, we made many choices and decisions with respect to development, and choosing monorepo architecture was one of them. In this article, we will take a closer look at when we should use a monorepo and when not for React Native and share a glimpse of our experience.

What is a monorepo

A monorepo (aka monolithic repository) is an architectural concept in which we use one repository for storing multiple projects instead of managing multiple repositories. While these projects may be related, they are often logically independent. For example, a single repository for web and mobile apps.

When to use monorepo

  • Code sharing & More collaboration: When you have a use case in which code sharing between different projects is required, monorepo can be helpful. As all code exists in a single repository, there is an increased likelihood of code reuse, less code duplication, and more teams working on shared infrastructure.
  • Ease of abstractions: Monorepos simplifies code abstraction. We can easily build layers of abstractions on top of existing code and third-party libraries and keep them in separate modules for usage across all the projects by simply importing them wherever needed and using API exposed from them. In this way, it also makes the code isolated from business logic and provides separation of concerns.
  • Easier to publish packages for independent code: If you want to publish a part of your project, you can do so with monorepo as your code can live in isolation, decoupled from other logic as an independent project when compared to the case in which you have this code in a single repository. In this case, pulling out pieces that are supposed to be published from a single code repository would be more complicated.
  • Easier dependency management: Monorepo helps prevent dependency conflicts, as all modules are hoisted and used from a single place. This also prevents the need to re-install dependencies in each project whenever you want to update them. This can also help with packages from node_modules being bundled twice in case tree-shaking is not configured correctly.
  • Atomic changes and easier refactors: Large-scale refactoring becomes easier with monorepo as a developer can update several packages or projects in a single commit as compared to when working with multiple repositories.

When not to use monorepo

  • When you have a simple use case for the app without any need to share code between multiple projects. It might also be overkill if you feel the logical separation of modules can be achieved with simple folder structuring instead of maintaining separate projects.
  • While creating POCs, MVPs, or small demo apps where the focus is on speed. Building with a monorepo approach requires maintenance and setup efforts from time to time, which can take time away from working on product features.
  • When different teams are working on different independent projects, it is better to have separate repositories than monorepo. Because with monorepo architecture, the size of the codebase will increase significantly making it difficult for developers to search through or navigate within the entire codebase once the project scales to a reasonable level.
  • Working with monorepo can involve a significant learning curve and can involve diving into your dependencies from time to time to make them work with your project. This bit is especially true for react native, as many native dependencies include hard-coded paths inside their Gradle build files. This causes issues with auto-linking and metro not being able to find certain modules while building.

Why we chose monorepo

  • While starting with the project, we had a clear goal — To share as much code as possible between the web and the mobile platform. And monorepo architecture helped us support segregating the codebase in a way, where we could reuse pieces apart from the UI layer between these two platforms.
  • Monorepo helped us manage dependencies better by hoisting them to the root level and sharing them across various packages.
  • Monorepo allowed us to write code without coupling business logic and services with our UI layer.
  • Structuring business logic into separate projects can also make orchestrating platform-specific features easier instead of adding Platform.OS checks everywhere.

How we use monorepo

While working with a monorepo architecture, you need a certain set of tools to manage how your packages interact with each other. We use Yarn Workspaces for this, as it works well with React Native. Other examples include turborepo, Nx, Lerna, pnpm which even go beyond repository management with features like build caching, changelog generation, better build performance, etc.

<project_root>
├── node_modules
├── package.json
└── packages
├── app
├── components
├── core
├── mobile
└── styling

The image above showcases what a monorepo project could look like, where each package depicts a separate project with its own package.json and other configuration files. We have app, components, core, mobile, and styling.

Core Package

This package contains the core services of our app, like classes and wrappers written for third-party libraries, for example, the network layer responsible for API calls, analytics, RUM services, logging, etc. No business logic is present in this package to make services reusable and easily pluggable into any apps we add into the monorepo.

App Package

This is where all of our app UI (screens, navigators, context, app hooks) and business logic live. This is kept separate from the react native project with the intention to re-use it in the web app.

Mobile Package

This is the package containing the react native project (along with native android and ios files). It has configuration files for the metro bundler, environment files (env), build scripts, and project initialization files (index.js and App.js). Its package.json file contains scripts for running the project along with all the dependencies that have native files. No business logic is written here, and it imports all of it from the app package declared above.

Components Package

All reusable UI components reside here. Separating out UI components this way also makes it possible to develop them in isolation from the actual screens enforcing reusability. Examples of components that reside here are — Card, Bottom Sheet, Carousel, Input, Button, List, Modal, Tooltip, etc.

Styling Package

All styling and design system-related configurations are stored in this package and used in the entire project from here only. It includes different colors, font families, text and custom variants, spacing values, and typography values as per our design system. It also contains a wrapper for shopify’s restyle, which we use as our third-party styling library.

Common problems we faced

  • While building the app, some dependencies had hard-coded paths to node_modules, assuming the structure was created by a react native CLI project. Fixing these issues required creating patches using something like patch-package
  • Some configuration is required to make the metro bundler compatible with Yarn workspaces. And why that? Because of metro bundler’s limitation to follow symlinks (Github issue) due to which metro is not able to detect dependencies that are hoisted since they are installed in mobile/node_modules as symlinks from <root>/node_modules. Also, metro won’t even be able to resolve other workspaces like app, components, etc., since they’re outside of the mobile directory.
    So to fix this problem, we have many metro configuration options available. And we can update the metro configuration file with the help of react-native-monorepo-tools and make metro compatible with Yarn workspaces.
    You can refer to this blog to help set it up.

Conclusion

So, as we have described monorepo has many advantages, but at the same time, they are not the right fit for every team. Understand your team’s strengths & weaknesses and your project’s use case to determine if a monorepo is the right choice.

Thank you for reading this article. If you enjoyed this article, please give a few claps so that it reaches more people who could benefit from it!

--

--

Ramanpreet Singh
SquadStack Engineering

A computer science student having a keen interest in full-stack development, data science, and automobiles. Love to read books and on a journey to explore life