Skip to content
useffect.sh
Book 30 min with a senior
Tooling

Monorepo

One repository holding several packages linked by the package manager (workspaces); needs care around Metro, autolinking, and dependency hoisting.

Definition

A monorepo is a single repository hosting several packages linked together by the package manager’s workspaces (Bun, npm, pnpm, or Yarn): one or more React Native apps, a shared component package, an API client, sometimes a website. For React Native it raises three specific problems. Metro has to resolve modules located outside the app folder; since SDK 52, Expo configures that automatically through expo/metro-config, and manual settings (watchFolders, nodeModulesPaths) should be removed. Autolinking has to find native modules hoisted to the root or installed in isolated mode; Expo added autolinkingModuleResolution in SDK 54, enabled automatically for monorepos since SDK 55. And exactly one copy of react and of each native module must exist, or you get runtime errors and broken builds. It replaces private packages published and versioned separately. An “Invalid hook call” after adding a shared package is the typical symptom.

In a real app

Diagnosis starts with npm ls react react-native (or the pnpm/yarn equivalent) from the app: two versions of react in the tree give the familiar “Invalid hook call”, and two copies of a native module break the iOS or Android build. A resolutions field (Yarn) or overrides (npm, pnpm) forces a single version. pnpm projects in isolated mode that keep hitting native errors can set nodeLinker: hoisted in pnpm-workspace.yaml, which the Expo docs offer as the fallback.

Native scripts are the other failure source: a Podfile or build.gradle referencing ../node_modules/react-native by relative path finds nothing when the package is hoisted. The right form uses require.resolve from Node to locate react-native and @react-native-community/cli-platform-ios, which recent Expo templates already do. A metro.config.js copied from a tutorial with watchFolders and disableHierarchicalLookup should be simplified, then npx expo start --clear to drop the cache.

In CI and on EAS Build, a monorepo needs the app folder specified and the root not excluded through .easignore. My rule for shared packages: TypeScript sources consumed directly (main pointing at src/index.ts), with no intermediate build step, as long as the package is not published.

Our services on this

Sources

All 35 glossary terms

One of these is blocking your app?

30 minutes with a senior to look at the problem in your code. Free.