Monorepo Setup Guide

 

This guide explains step by step how to set up a monorepo containing:

  • ๐Ÿ“ฑ A React Native mobile app
  • ๐ŸŒ A React Web app (Vite)
  • ๐Ÿ“ฆ Shared packages for:
    • Design tokens (colors, spacing, fonts)
    • Network layer (Axios)

It also covers common pitfallsPNPM workspace quirks, and troubleshooting steps discovered during setup.


๐Ÿ“Œ Why Monorepo?

A monorepo allows multiple related projects to live in a single Git repository, enabling:

  • Shared code without publishing packages
  • Consistent dependency versions
  • Atomic changes (one PR for app + shared code)
  • Easier long-term scaling

๐Ÿง  High-Level Architecture

my-monorepo/
├── apps/
│   ├── mobile/               # React Native app
│   └── web/                  # React Web app (Vite)
│
├── packages/
│   ├── design-tokens/        # Shared colors, spacing, fonts
│   │   └── src/
│   │       ├── colors.ts
│   │       ├── spacing.ts
│   │       └── index.ts
│   │
│   └── network/              # Axios client
│
├── package.json              # Root config
├── pnpm-workspace.yaml
├── metro.config.js           # Required for RN monorepo
└── README.md

0️⃣ Prerequisites

Ensure the following are installed:

  • Node.js v18+
  • PNPM (global)
npm install -g pnpm
  • React Native CLI environment
    • Android Studio / Xcode properly configured

1️⃣ Create Monorepo Root

mkdir my-monorepo && cd my-monorepo
pnpm init

Root package.json

{
  "name": "my-monorepo",
  "private": true
}

Why private: true?

  • Prevents accidental publishing
  • Required for PNPM workspace roots

2️⃣ Enable PNPM Workspaces

Create pnpm-workspace.yaml at the root:

packages:
  - apps/*
  - packages/*

This tells PNPM that:

  • Everything under apps/ and packages/ is part of the workspace
  • Local packages should be linked, not fetched from npm

3️⃣ Create React Native App

mkdir apps
cd apps
npx react-native init mobile

⚠️ Important

  • Do NOT run npm install inside the app
  • Dependencies are managed by PNPM at the root

4️⃣ Create React Web App (Vite)

cd apps
pnpm create vite web --template react-ts

Install dependencies from the root:

cd ..
pnpm install

5️⃣ Create Shared Packages Folder

mkdir packages

6️⃣ Setup Design Tokens Package (Correct Way)

Folder Structure

mkdir -p packages/design-tokens/src
packages/design-tokens/
├── package.json
└── src/
    ├── colors.ts
    ├── spacing.ts
    └── index.ts

packages/design-tokens/package.json

{
  "name": "@vjoshi/design-tokens",
  "version": "0.0.1",
  "private": true,
  "main": "src/index.ts"
}

Token Files

// src/colors.ts
export const colors = {
  primary: "#2563eb",
  danger: "#dc2626"
};
// src/spacing.ts
export const spacing = {
  sm: 8,
  md: 16,
  lg: 24
};

Barrel Export (CRITICAL)

// src/index.ts
export * from "./colors";
export * from "./spacing";

๐Ÿ“Œ Why this is required

  • PNPM links the package, but exports control visibility
  • Without index.ts, nothing can be imported by apps

7️⃣ Setup Shared Network Layer (Axios)

mkdir packages/network

packages/network/package.json

{
  "name": "@vjoshi/network",
  "version": "0.0.1",
  "private": true,
  "dependencies": {
    "axios": "^1.6.0"
  }
}

๐Ÿ“Œ Axios is treated as an implementation detail of the network layer
๐Ÿ“Œ Apps do NOT import Axios directly

Axios Client

import axios from "axios";

export const httpClient = axios.create({
  baseURL: "https://api.example.com",
  timeout: 10000
});

8️⃣ Consume Shared Packages in Apps

Mobile App (apps/mobile/package.json)

{
  "dependencies": {
    "@vjoshi/design-tokens": "workspace:*",
    "@vjoshi/network": "workspace:*"
  }
}

Web App (apps/web/package.json)

{
  "dependencies": {
    "@vjoshi/design-tokens": "workspace:*",
    "@vjoshi/network": "workspace:*"
  }
}

Run install only from root:

pnpm install

9️⃣ Configure Metro for Monorepo (CRITICAL)

Create metro.config.js at the repo root:

const path = require("path");

module.exports = {
  watchFolders: [path.resolve(__dirname, "packages")],
  resolver: {
    nodeModulesPaths: [path.resolve(__dirname, "node_modules")]
  }
};

๐Ÿ“Œ Without this, React Native will NOT see shared packages.


๐Ÿ”Ÿ Running the Apps

React Native

pnpm --filter @vjoshi/mobile start
pnpm --filter @vjoshi/mobile android
pnpm --filter @vjoshi/mobile ios

Web App

pnpm --filter @vjoshi/web dev

๐Ÿงช Importing Design Tokens (Example)

import { colors, spacing } from "@vjoshi/design-tokens";

๐Ÿง  Core Monorepo Rules (READ THIS FIRST)

  1. PNPM is the only allowed package manager
  2. No dependencies should be installed in the root package.json
  3. Each package owns the dependencies it uses
  4. All installs happen via PNPM from the repo root
  5. npm / yarn must never be used

๐Ÿ“ฆ Dependency Ownership Model

Golden Rule

If a package imports a dependency, that dependency must be declared in that package’s package.json.

This applies even inside a monorepo.


✅ Installing Dependencies (Correct Way)

Platform‑specific dependency (React Native)

pnpm --filter @vjoshi/mobile add react-native-gesture-handler

Web‑only dependency

pnpm --filter @vjoshi/web add tailwindcss

Shared package dependency

pnpm --filter @vjoshi/design-tokens add lodash
pnpm --filter @vjoshi/network add lodash

๐Ÿ“Œ Important

  • Installing the same dependency in multiple packages is correct
  • PNPM stores only one physical copy
  • Packages are linked via symlinks

❌ What NOT To Do

npm install lodash
npm install axios

or

cd apps/mobile
npm install something

This will:

  • Create package-lock.json
  • Break the PNPM lockfile
  • Cause Metro / bundler inconsistencies

๐Ÿงฉ Shared Dependencies Example (lodash)

Scenario

  • design-tokens uses lodash
  • network uses lodash

Correct Setup

pnpm --filter @vjoshi/design-tokens add lodash
pnpm --filter @vjoshi/network add lodash

✔ Explicit
✔ Safe
✔ Scalable

PNPM will still store lodash only once internally.


๐Ÿšซ Root package.json Dependency Policy

Root dependencies are NOT allowed

The root package.json is used only for:

  • Workspace configuration
  • Scripts
  • Tooling enforcement

❌ Do NOT install runtime dependencies at root.

✔ Install dependencies only inside apps or packages.


๐Ÿ›ก️ Preventing npm Installs (MANDATORY)

To avoid accidental npm usage, we enforce PNPM via a preinstall script.


๐Ÿ“œ Preinstall Guard Script

Create this file at repo root:

scripts/enforce-pnpm.js

if (!process.env.npm_execpath || !process.env.npm_execpath.includes('pnpm')) {
  console.error('\n❌ This repository only allows PNPM.');
  console.error('๐Ÿ‘‰ Please use pnpm install / pnpm add instead.\n');
  process.exit(1);
}

๐Ÿ”ง Register Preinstall Script

Root package.json

{
  "name": "my-monorepo",
  "private": true,
  "scripts": {
    "preinstall": "node scripts/enforce-pnpm.js"
  }
}

๐Ÿ” Apply Preinstall to All Packages

Add the same script reference to:

  • apps/mobile/package.json
  • apps/web/package.json
  • All shared packages under packages/

Example

{
  "scripts": {
    "preinstall": "node ../../scripts/enforce-pnpm.js"
  }
}

๐Ÿงน Recovery If npm Was Used Accidentally

rm -rf node_modules
rm -f package-lock.json
rm -f apps/**/package-lock.json
pnpm install
npx react-native start --reset-cache

๐Ÿง  Final Mental Model

One monorepo → one package manager → explicit dependencies → predictable builds


๐Ÿ› ️ Troubleshooting & Common Issues

❌ PNPM tries to fetch local package from npm (404 error)

Cause

  • Dependency declared as "*" instead of workspace reference

Fix

"@vjoshi/design-tokens": "workspace:*"

❌ pnpm list --filter mobile shows “No projects matched”

Cause

  • --filter matches package name, not folder name

Fix Check the name in apps/mobile/package.json, then run:

pnpm --filter <package-name> list

❌ Tokens exist but cannot be imported

Cause

  • No entry file (index.ts)
  • No main field in package.json

Fix

  • Add src/index.ts
  • Export all tokens
  • Point main to it

❌ Changes in shared packages not reflected in RN app

Cause

  • Metro cache

Fix

npx react-native start --reset-cache

๐Ÿง  Final Summary

  • PNPM workspaces require explicit workspace:* references
  • Linking a package is NOT enough — exports matter
  • Design tokens must have:
    • Entry file
    • Explicit exports
  • Metro configuration and cache resets are mandatory in RN monorepos
  • Never run npm install inside app folders

Happy building ๐Ÿš€

Comments

Popular posts from this blog

๐Ÿšซ React Native Style Can Quietly Hurt Performance

๐Ÿง  React Native New Architecture Explained