Monorepo Setup & Code Ownership
📖 Concept
Monorepos allow you to manage multiple packages (shared UI kit, API client, analytics, feature modules) in a single repository with shared tooling and atomic commits.
When to use a monorepo for React Native:
- You have a shared component library used across multiple apps
- You want to share code between RN mobile and React web
- You have independent teams that need clear package boundaries
- You want atomic changes across multiple packages (update an API + consumers in one PR)
Popular monorepo tools:
- Turborepo — Incremental builds, remote caching, simple config
- Nx — More features, plugin ecosystem, affected-based testing
- Yarn/npm workspaces — Basic workspace management, dependency hoisting
Code ownership strategy: Code ownership defines WHO is responsible for WHAT code. At scale, this prevents:
- "Tragedy of the commons" — nobody owns shared code, quality degrades
- Bottleneck reviews — one person reviews all PRs
- Unreviewed changes to critical infrastructure
CODEOWNERS file pattern:
# .github/CODEOWNERS
# Platform team owns core infrastructure
/packages/platform/** @platform-team
/packages/design-system/** @design-system-team
# Feature teams own their features
/apps/mobile/src/features/checkout/** @payments-team
/apps/mobile/src/features/feed/** @feed-team
/apps/mobile/src/features/auth/** @auth-team
# Shared packages need platform team review
/packages/api-client/** @platform-team
/packages/analytics/** @platform-team @data-team
💻 Code Example
1// === MONOREPO STRUCTURE ===23/*4my-app/5├── apps/6│ ├── mobile/ ← React Native app7│ │ ├── src/8│ │ ├── android/9│ │ ├── ios/10│ │ └── package.json11│ └── web/ ← React web app (optional)12│ ├── src/13│ └── package.json14├── packages/15│ ├── ui-kit/ ← Shared components16│ │ ├── src/17│ │ │ ├── Button/18│ │ │ ├── Input/19│ │ │ └── index.ts20│ │ ├── package.json21│ │ └── tsconfig.json22│ ├── api-client/ ← Shared API layer23│ │ ├── src/24│ │ │ ├── client.ts25│ │ │ ├── endpoints.ts26│ │ │ └── types.ts27│ │ └── package.json28│ ├── shared-types/ ← Shared TypeScript types29│ │ ├── src/30│ │ └── package.json31│ └── analytics/ ← Shared analytics32│ ├── src/33│ └── package.json34├── turbo.json ← Turborepo config35├── package.json ← Root workspace config36└── tsconfig.base.json ← Shared TS config37*/3839// turbo.json — Build pipeline40const turboConfig = {41 "$schema": "https://turbo.build/schema.json",42 "pipeline": {43 "build": {44 "dependsOn": ["^build"], // Build dependencies first45 "outputs": ["dist/**"]46 },47 "test": {48 "dependsOn": ["build"],49 "outputs": []50 },51 "lint": {52 "outputs": []53 },54 "dev": {55 "cache": false,56 "persistent": true57 }58 }59};6061// packages/api-client/src/client.ts — Shared across mobile + web62import type { APIResponse, RequestConfig } from '@myapp/shared-types';6364export class APIClient {65 constructor(66 private baseURL: string,67 private getToken: () => Promise<string | null>,68 ) {}6970 async request<T>(config: RequestConfig): Promise<APIResponse<T>> {71 const token = await this.getToken();7273 const response = await fetch(`${this.baseURL}${config.endpoint}`, {74 method: config.method,75 headers: {76 'Content-Type': 'application/json',77 ...(token ? { Authorization: `Bearer ${token}` } : {}),78 ...config.headers,79 },80 body: config.body ? JSON.stringify(config.body) : undefined,81 });8283 if (!response.ok) {84 throw new APIError(response.status, await response.text());85 }8687 return response.json();88 }89}9091// apps/mobile/src/config/api.ts — Mobile-specific setup92import { APIClient } from '@myapp/api-client';93import { getAuthToken } from '../features/auth';9495export const apiClient = new APIClient(96 'https://api.example.com/v1',97 getAuthToken,98);
🏋️ Practice Exercise
Monorepo & Ownership Exercises:
- Set up a Turborepo monorepo with a React Native app and a shared UI package
- Create a shared TypeScript types package used by both mobile app and API client
- Set up a CODEOWNERS file for a 3-team organization
- Configure ESLint import restrictions to enforce package boundaries
- Implement a shared analytics package with platform-specific adapters (mobile/web)
- Set up CI that only runs tests for packages affected by a PR (using Turborepo or Nx affected)
⚠️ Common Mistakes
Not configuring Metro bundler correctly for monorepo — Metro needs explicit watchFolders for packages outside the app directory
Circular dependencies between packages — shared-types imports from api-client which imports from shared-types
Not defining clear package boundaries — teams start importing internal files from other packages instead of the public API
Making everything a shared package — only share when 2+ consumers exist; premature abstraction adds complexity
Not setting up consistent tooling (ESLint, TypeScript, Jest configs) at the root level — each package evolves its own config
💼 Interview Questions
🎤 Mock Interview
Mock interview is powered by AI for Monorepo Setup & Code Ownership. Login to unlock this feature.