One Repository for Shared React Native Code
Shopify’s mobile teams maintain multiple React Native apps—Shop, Inbox, Point of Sale, Shopify Mobile, and Local Delivery—that serve different business needs but rely on overlapping functionality, such as login flows and core building blocks. Extracting this shared code into a common place promises faster product development, yet the setup burden often blocks the effort. Creating a new repository involves establishing CI, distribution, and tooling configuration for Jest, ESLint, and Babel, which can feel like a high hurdle before any real code gets written.
The React Native Foundations team took on simplifying this process internally. The result is a centralized structure where teams can contribute shared packages with minimal friction, leaving them to focus on the package content rather than the surrounding infrastructure.
Why a Monorepo Won Over Separate Repositories
Weighing multi-repo against a monorepo approach, the decision came down to maintenance overhead and onboarding ease. A single monorepo offered significant advantages:
- Lower maintenance costs: One shared process supports all packages, meaning an upgrade to React Native or React is handled once, not across ten separate repositories.
- Reduced entry barriers: Developers get a ready package template, central documentation, and tooling, so they don't waste time configuring CI pipelines or deciding on package structure.
Centralizing configuration and workflows means contributors benefit from improvements made once, for everyone. This frees the foundations team to build conveniences like automatic documentation generation and a fixture app for real-world testing.
Architecture and Tooling Inside the Monorepo
The repository houses npm packages with native iOS and Android code, a fixture app for testing these packages in an actual application, and a documentation site. Its configuration supports hot reloading for package code and enables the fixture app to reference local packages seamlessly.
For type management, packages are authored in TypeScript but distributed as JavaScript with declaration files. TypeScript's project references handle cross-package imports at the IDE and compiler level, as defined in each package's tsconfig.json. Tooling handles the rest:
- Yarn Workspaces maps project dependencies in each package's
package.json. - Lerna resolves the dependency order and manages publishing new versions to the registry.
- TypeScript, Babel, Jest, and ESLint configs are hoisted to the root level for consistency and easier collaboration.
The fixture app itself uses standard React Native setup with Metro, Babel, CocoaPods, and Gradle, but with custom adjustments to integrate local packages:
babel.config.jsleverages the module-resolver plugin to resolve project references.metro.config.jsexposes package directories to Metro, enabling hot reload on package edits.- The
Podfilelocates and includes native Pods for local packages manually, bypassing React Native autolinking.
Testing happens locally via the fixture app. Developers can also generate internal Shipit Mobile builds, known as Snapshot builds, which the whole company can install via a QR code for hands-on testing.
CI infrastructure is auto-generated from package contents, creating standardized pipelines that handle build, test, type checking, and linting for TypeScript, Kotlin, and Swift. A dependency graph between packages drives these pipelines, ensuring that only modified packages and their dependents are rebuilt.
Generating New Packages
Documentation alone wasn't enough to get developers over the initial hump of starting a new package. To bootstrap this process further, the repo includes a script built with PlopJS that scaffolds fresh packages from a template. The template resembles the React Native community one but is customized for Shopify's needs.
Running the script prompts the developer with questions and generates:
- A ready-to-use package skeleton extending the monorepo's default configuration.
- Auto-generated CI pipelines tailored to the package.
This automation guarantees consistency across packages because everything is predefined, adhering to the community template. For the foundations team, it means maintaining and improving a single workflow, cutting down on long-term support.
Documentation as a Built-in Feature
Documentation shouldn't be an afterthought, so the monorepo makes it easy to maintain. A statically generated website built with Gatsby hosts all package documentation. Each package has a page auto-populated with metadata from its package.json, including:
- The package name and its dependencies.
- Installation commands, encompassing peer dependencies.
- An interactive dependency graph of the repo's packages.
These built-in sections keep the documentation consistent across the board. To add tailored content, contributors can create optional Markdown files in the package's documentation/ folder for specific needs:
installation.mdxfor extra setup steps.getting-started.mdxfor usage guides.troubleshooting.mdxfor common issues and fixes.
Release and Versioning Approach
Lerna orchestrate the release workflow, which uses independent versioning based on changes since the last release. The process updates changelogs and runs a script for version bumping across affected modules.
Versioning happens locally with two npm lifecycle scripts: preversion validates that changelogs are current before the version bump; version runs post-update, refreshing the readme and executing pod install with the new versions. Following this, a new release commit and tags are pushed to the main branch, and pressing “Publish” distributes packages to the internal registry.
While the workflow functions, it involves manual steps. The plan is to make releases automatic upon a merge to the main branch. Potential refinements include:
- Adopting conventional commits.
- Automating changelog generation.
- Setting up a GitHub action to prepare a release commit on every merge, triggering a Lerna commit and pushing it to main.
- Following that with a scheduled package release.
Keeping main shippable is a priority, and these future steps aim to reduce friction even further for contributors to shared React Native code.
What the Package Program Achieved — and What It Didn’t
The extraction strategy met its stated goal. Reusing code at Shopify now means accessing shared tooling, infrastructure, and maintenance from the React Native Foundations team, plus a streamlined contribution workflow. Product teams can write code directly into the foundation without waiting on the core team to do it for them.
The numbers reflect that openness: since June 2020, 17 React Native packages have been built, with 10 contributed by product teams.
Lessons from Working Around React Native Tooling
Shopify’s setup is not what React Native’s tooling was designed for. The team found the APIs flexible enough to reach a configuration they were comfortable with, but it required effort on their side. That means staying attentive to friction points and continuing to smooth them out as the tooling evolves.
Splitting the Monorepo Into Thematic Groups
The experience of Shopify’s Web Foundation team and the React Native Foundations team’s own observations both point in one direction: one giant monorepo is not the right end state. Instead, multiple monorepos, each containing thematically related packages, make more sense. A talk from Microsoft at React Native EU 2021 backed this up, describing multiple monorepos as a natural evolution for large React Native codebases.
Shopify has already moved in that direction. There are now two monorepos: a main one for loosely coupled utilities and Shopify-specific features, and a second dedicated to performance-related packages. As more monorepos appear, the open question becomes how to share pieces across them while keeping the benefits that brought Shopify to monorepos in the first place.



