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 pitfalls, PNPM workspace quirks, and troubleshooting steps discovered during setup.
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
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
Ensure the following are installed:
- Node.js v18+
- PNPM (global)
npm install -g pnpm- React Native CLI environment
- Android Studio / Xcode properly configured
mkdir my-monorepo && cd my-monorepo
pnpm init{
"name": "my-monorepo",
"private": true
}Why private: true?
- Prevents accidental publishing
- Required for PNPM workspace roots
Create pnpm-workspace.yaml at the root:
packages:
- apps/*
- packages/*This tells PNPM that:
- Everything under
apps/andpackages/is part of the workspace - Local packages should be linked, not fetched from npm
mkdir apps
cd apps
npx react-native init mobile- Do NOT run
npm installinside the app - Dependencies are managed by PNPM at the root
cd apps
pnpm create vite web --template react-tsInstall dependencies from the root:
cd ..
pnpm installmkdir packagesmkdir -p packages/design-tokens/srcpackages/design-tokens/
├── package.json
└── src/
├── colors.ts
├── spacing.ts
└── index.ts
{
"name": "@vjoshi/design-tokens",
"version": "0.0.1",
"private": true,
"main": "src/index.ts"
}// src/colors.ts
export const colors = {
primary: "#2563eb",
danger: "#dc2626"
};// src/spacing.ts
export const spacing = {
sm: 8,
md: 16,
lg: 24
};// 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
mkdir packages/network{
"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
import axios from "axios";
export const httpClient = axios.create({
baseURL: "https://api.example.com",
timeout: 10000
});{
"dependencies": {
"@vjoshi/design-tokens": "workspace:*",
"@vjoshi/network": "workspace:*"
}
}{
"dependencies": {
"@vjoshi/design-tokens": "workspace:*",
"@vjoshi/network": "workspace:*"
}
}Run install only from root:
pnpm installCreate 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.
pnpm --filter @vjoshi/mobile start
pnpm --filter @vjoshi/mobile android
pnpm --filter @vjoshi/mobile iospnpm --filter @vjoshi/web devimport { colors, spacing } from "@vjoshi/design-tokens";- PNPM is the only allowed package manager
- No dependencies should be installed in the root
package.json - Each package owns the dependencies it uses
- All installs happen via PNPM from the repo root
- npm / yarn must never be used
If a package imports a dependency, that dependency must be declared in that package’s
package.json.
This applies even inside a monorepo.
pnpm --filter @vjoshi/mobile add react-native-gesture-handlerpnpm --filter @vjoshi/web add tailwindcsspnpm --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
npm install lodash
npm install axiosor
cd apps/mobile
npm install somethingThis will:
- Create
package-lock.json - Break the PNPM lockfile
- Cause Metro / bundler inconsistencies
design-tokensuses lodashnetworkuses lodash
pnpm --filter @vjoshi/design-tokens add lodash
pnpm --filter @vjoshi/network add lodash✔ Explicit
✔ Safe
✔ Scalable
PNPM will still store lodash only once internally.
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.
To avoid accidental npm usage, we enforce PNPM via a preinstall script.
Create this file at repo root:
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);
}{
"name": "my-monorepo",
"private": true,
"scripts": {
"preinstall": "node scripts/enforce-pnpm.js"
}
}Add the same script reference to:
apps/mobile/package.jsonapps/web/package.json- All shared packages under
packages/
{
"scripts": {
"preinstall": "node ../../scripts/enforce-pnpm.js"
}
}rm -rf node_modules
rm -f package-lock.json
rm -f apps/**/package-lock.json
pnpm install
npx react-native start --reset-cacheOne monorepo → one package manager → explicit dependencies → predictable builds
Cause
- Dependency declared as
"*"instead of workspace reference
Fix
"@vjoshi/design-tokens": "workspace:*"Cause
--filtermatches package name, not folder name
Fix Check the name in apps/mobile/package.json, then run:
pnpm --filter <package-name> listCause
- No entry file (
index.ts) - No
mainfield inpackage.json
Fix
- Add
src/index.ts - Export all tokens
- Point
mainto it
Cause
- Metro cache
Fix
npx react-native start --reset-cache- 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 installinside app folders
Happy building ๐
Comments
Post a Comment