React Native monorepo u 2026: Turborepo, pnpm workspaces i Expo za platform timove

Vodič za React Native monorepo u 2026: pnpm workspaces, Turborepo pipelines, Metro konfiguracija za monorepo, Expo autolinking, EAS Build root setup i remote cache koji CI vrijeme skraćuje s 12 na 3 minute. Primjeri iz produkcijskog fintech monorepoa.

RN Monorepo 2026: Turborepo + pnpm Vodič

Ažurirano: 16. rujna 2026.

React Native monorepo je jedan git repozitorij koji drži više aplikacija (Expo, bare RN, Next.js web) i dijeljenih paketa (UI, API klijent, tipovi) povezanih pnpm ili yarn workspaces sustavom, dok task runner poput Turborepoa cachira i paralelizira builds. U 2026. to je zadana arhitektura za svaki tim koji održava iOS, Android i web klijent nad istim backendom. Rezultat: jedan izvor tipa, jedno mjesto za refactor i drastično kraći CI. Ovaj vodič pokriva pnpm workspaces, Turborepo, Metro konfiguraciju s watchFolders, Expo autolinking u monorepou i EAS Build koji zna gdje mu je root.

  • Za nove React Native projekte u 2026. koristi pnpm workspaces + Turborepo. Nx dolazi u obzir tek kad monorepo ima 20+ paketa i vlastite generatore.
  • Metro watchFolders mora pokazivati na root monorepoa, a nodeModulesPaths na sve razine node_modules foldera.
  • Expo autolinking radi u monorepou iz kutije od SDK 50, ali samo ako package.json aplikacije ima točne expo/expo-modules-core verzije.
  • EAS Build zahtijeva eas.json na razini aplikacije s projectRoot postavkom i NODE_ENV=production u env bloku.
  • Turborepo remote cache s turbo login smanjuje CI vrijeme za tipičan RN monorepo s 12 na 3 minute na hitovima.
  • Dijeljeni TypeScript setup ide preko tsconfig.base.json i references, a ne preko copy-paste konfiguracije po paketu.

Zašto React Native monorepo u 2026.

Iskreno, do prije dvije godine u fintech platformi koju arhitektiram imali smo tri repozitorija: mobile (Expo), web (Next.js) i shared-types. Svaka izmjena TypeScript modela značila je bump paketa u shared-types, dva pull requesta i tjedan dana čekanja da netko poveže sve zajedno. Nakon migracije na jedan monorepo, tipska promjena je jedan PR koji prolazi sve testove istovremeno: mobilne, web i backend. To je jedini razlog zašto monorepoi postoje.

U 2026. React Native tim koji ne koristi monorepo najčešće ima jedan od tri problema: dupliciranu poslovnu logiku između web i mobile klijenata, neusklađen API klijent (naive fetch u jednom, tRPC u drugom), ili prekomjerno vrijeme CI-ja zbog ne-inkrementalnog buildanja. Monorepo s Turborepo task grafom sve tri stvari rješava direktno. Drugi razlog: Expo Router v6 sad podržava dijeljenje ekrana s Next.js aplikacijom kroz expo-router/web, što praktički zahtijeva monorepo za zdrav dev-loop.

Ključna nuansa, i tu se ekipa često sapliće. Monorepo nije "sve u jednom repo-u". To je alat koji definira jasne granice između paketa. Svaki paket ima svoj package.json, svoj tsconfig.json i svoje eksplicitne dependencije. Ako to izgubiš, dobit ćeš loše polje minesa: cirkularne importe, nejasne granice odgovornosti i CI koji rebuilda sve na svaku promjenu README-ja.

Turborepo vs Nx: što odabrati u 2026.

Za React Native monorepo srednje veličine (2–8 paketa) Turborepo je bolji izbor u 2026. Nx dolazi u obzir kad monorepo pređe 20+ paketa, kad tim treba generatore (nx g @nx/react-native:app), ili kad postoji potreba za graph-based analizom utjecaja promjena između udaljenih paketa. Za sve ostalo, Turborepo daje 80% funkcionalnosti uz 10% konfiguracije.

KarakteristikaTurborepo 2.xNx 20.x
Krivulja učenjaNiska (jedna turbo.json)Srednja/visoka (projektni JSON-ovi + plugini)
Remote cacheUgrađen (Vercel besplatan tier)Nx Cloud (besplatan uz limit)
Generatori kodaNema (koristi degit)Bogat generator sustav
Podrška za React NativeRadi out-of-the-boxIma @nx/react-native plugin
Podrška za ExpoNative, bez pluginaPreko @nx/expo plugina
KonfiguracijaDeklarativna, minimalnaImperativna, opširna
Idealno za2–10 paketa, brzi start10+ paketa, više timova

U mom trenutnom timu koristimo Turborepo 2.3 s pnpm workspaces. Postavka je zapravo 90 minuta posla, a remote cache preko Vercela nam štedi otprilike sat vremena CI kredita dnevno. Kad smo raspravljali o Nx-u, presudno je bilo što nitko u timu nije htio dodatnu apstrakciju samo da bi generirao mapu. degit template rješava taj problem u jednoj naredbi.

Postavljanje pnpm workspaces korak po korak

Startna struktura koju preporučam za novi React Native monorepo izgleda ovako:

my-platform/
├── apps/
│   ├── mobile/          # Expo aplikacija
│   └── web/             # Next.js
├── packages/
│   ├── ui/              # dijeljeni React komponenti
│   ├── api-client/      # tRPC ili OpenAPI klijent
│   ├── config/          # ESLint, Prettier, TS baseline
│   └── types/           # domenski tipovi
├── pnpm-workspace.yaml
├── package.json
├── turbo.json
└── tsconfig.base.json

Prvi korak je pnpm-workspace.yaml koji definira gdje pnpm traži pakete:

# pnpm-workspace.yaml
packages:
  - "apps/*"
  - "packages/*"

Zatim root package.json označi kao privatan i doda Turborepo:

{
  "name": "my-platform",
  "private": true,
  "packageManager": "[email protected]",
  "scripts": {
    "dev": "turbo run dev",
    "build": "turbo run build",
    "lint": "turbo run lint",
    "typecheck": "turbo run typecheck"
  },
  "devDependencies": {
    "turbo": "^2.3.0",
    "typescript": "5.6.3"
  }
}

Ključno je koristiti pnpm 9+ za node-linker=hoisted mod koji je potreban zbog Expo autolinkinga. Sirov pnpm symlink layout (isolated) ne radi dobro s Metroom u 2026. Dodaj .npmrc u root:

# .npmrc
node-linker=hoisted
public-hoist-pattern[]=*expo*
public-hoist-pattern[]=*react-native*
public-hoist-pattern[]=@react-native*

Metro konfiguracija za monorepo

Metro po default-u gleda samo u node_modules tvoje aplikacije, što je fatalna pretpostavka u monorepou gdje su dependencije podignute u root. Rješenje je proširiti watchFolders i nodeModulesPaths. Ovako izgleda apps/mobile/metro.config.js za pnpm setup:

// apps/mobile/metro.config.js
const { getDefaultConfig } = require('expo/metro-config');
const path = require('path');

const projectRoot = __dirname;
const workspaceRoot = path.resolve(projectRoot, '../..');

const config = getDefaultConfig(projectRoot);

// #1: Reci Metrou da promatra cijeli monorepo
config.watchFolders = [workspaceRoot];

// #2: Reci Metrou gdje da traži node_modules (redoslijed je bitan)
config.resolver.nodeModulesPaths = [
  path.resolve(projectRoot, 'node_modules'),
  path.resolve(workspaceRoot, 'node_modules'),
];

// #3: Onemogući hijerarhijsko traženje da izbjegnemo duple pakete
config.resolver.disableHierarchicalLookup = true;

module.exports = config;

Bez disableHierarchicalLookup, Metro može učitati dvije verzije Reacta (jednu iz apps/mobile/node_modules, drugu iz roota) i eksplodirati s klasičnom "Invalid hook call" greškom u produkciji. Vidio sam taj slom uživo prošli kvartal. Dijagnostika je gubila ekipi cijeli dan dok nismo posumnjali na duple Reacte kroz npm ls react.

Za expo-router u monorepou ide još jedna postavka: EXPO_ROUTER_APP_ROOT environment varijabla mora pokazivati na app folder aplikacije, jer autodetekcija zna zbuniti hoisted node_modules. Ako koristiš expo-router v6, provjeri službenu Expo dokumentaciju za monorepoe za trenutne preporuke, one se mijenjaju s SDK verzijama.

Kako Expo radi u monorepou

Expo autolinking je proces kojim se native moduli iz node_modules automatski povezuju s iOS Xcode projektom i Android Gradle buildom. U 2026. s Expo SDK 52+ radi u monorepou iz kutije, ali samo ako aplikacijski package.json deklarira sve Expo module direktno, a ne preko packages/*. Ako u packages/ui/package.json stavim expo-image, a ne stavim ga u apps/mobile/package.json, autolinker ga neće naći.

Zadano pravilo: native moduli žive u aplikaciji, ne u dijeljenim paketima. Dijeljeni paketi mogu importirati Expo module (npr. <Image/> iz expo-image), ali sam paket mora biti u peerDependencies:

// packages/ui/package.json
{
  "name": "@my-platform/ui",
  "version": "0.1.0",
  "main": "./src/index.ts",
  "types": "./src/index.ts",
  "peerDependencies": {
    "expo-image": "*",
    "react": "*",
    "react-native": "*"
  }
}

Aplikacija onda instalira expo-image u svoj package.json i time zadovoljava peer dep. Ovaj obrazac vrijedi za sve native module: react-native-reanimated, react-native-mmkv, expo-sqlite i slično. Ako imaš pitanja oko toga koji je tvoj slučaj, u našem vodiču za lokalne baze u React Native pokazano je kako expo-sqlite pravilno smjestiti u monorepou.

Dijeljeni paketi: UI, API klijent, tipovi

Dobar monorepo ima jasnu granicu između aplikacija (apps/) i biblioteka (packages/). U mojoj postavci koristim tri kategorije dijeljenih paketa:

  1. Domenski tipovi (@my-platform/types): čisti TypeScript tipovi za User, Transaction, Account. Nema React, nema React Native. Ovaj paket može konzumirati backend, mobile i web.
  2. API klijent (@my-platform/api-client): tRPC ili OpenAPI generated klijent. Ovisi o types. Ne ovisi o React Native, ali ovisi o @tanstack/react-query ako izlaže hooks. Za produbljivanje state managementa preko monorepoa pogledaj naš vodič za state management s Zustand i TanStack Query.
  3. UI komponente (@my-platform/ui): Button, Card, Input. Piše se u React Native primitivima (View, Text) i renderira i na webu preko react-native-web.

Za internal packages preporučam direktni izvorni kod pristup ("main": "./src/index.ts") umjesto pre-buildanja s tsup ili tsc. Metro razumije TypeScript izravno, TanStack query razumije, Next.js razumije preko transpilePackages. Buildanje unutar monorepoa je gubitak vremena osim ako paket ide na npm.

// tsconfig.base.json (root)
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "strict": true,
    "jsx": "react-native",
    "skipLibCheck": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "esModuleInterop": true,
    "paths": {
      "@my-platform/*": ["./packages/*/src"]
    }
  }
}

Aplikacije samo naslijede: "extends": "../../tsconfig.base.json" plus svoja specifičnost. Type-safety kroz cijeli monorepo je stvarna vrijednost, jer svaki refactor tipa u types paketu odmah pokazuje sve pogodene lokacije u tsc --build.

EAS Build i CI iz monorepoa

EAS Build zna raditi s monorepoima, ali treba mu eksplicitno reći gdje mu je root. Bez toga, EAS pokušava npm install u apps/mobile/ i propušta root pnpm-lock.yaml. Rješenje je eas.json u apps/mobile/ s dodanim cli.appVersionSource i build.production.env:

// apps/mobile/eas.json
{
  "cli": {
    "version": ">= 12.0.0",
    "appVersionSource": "remote"
  },
  "build": {
    "production": {
      "node": "20.11.0",
      "env": {
        "EXPO_NO_TELEMETRY": "1"
      },
      "cache": {
        "paths": ["node_modules"]
      }
    }
  }
}

Ključna postavka je .easignore u root-u monorepoa koji ne ignorira packages/. EAS mora imati pristup svim dijeljenim paketima. Također, u apps/mobile/package.json dodaj skriptu koja instalira dependencije s roota:

{
  "scripts": {
    "eas-build-pre-install": "cd ../.. && pnpm install --frozen-lockfile"
  }
}

EAS respektuje pre-install hook i pokreće ga prije Xcode/Gradle build koraka. Detaljno o EAS Buildu i submisiji na trgovine pokrivamo u vodiču za EAS Build i Submit, uključujući i handling monorepoa u kombinaciji s custom credential managementom.

Turborepo pipelines i remote cache

Turborepo se konfigurira preko jednog turbo.json u rootu. Osnovna postavka koja pokriva 90% slučajeva:

// turbo.json
{
  "$schema": "https://turborepo.com/schema.json",
  "tasks": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": ["dist/**", ".next/**", "!.next/cache/**"],
      "env": ["NODE_ENV", "EXPO_PUBLIC_*"]
    },
    "lint": {
      "dependsOn": ["^lint"],
      "outputs": []
    },
    "typecheck": {
      "dependsOn": ["^typecheck"],
      "outputs": ["**/*.tsbuildinfo"]
    },
    "test": {
      "dependsOn": ["^build"],
      "outputs": ["coverage/**"]
    },
    "dev": {
      "cache": false,
      "persistent": true
    }
  }
}

Sintaksa ^build znači "prvo buildaj sve dependencije ovog paketa". Turborepo automatski gradi topological graph i paralelizira zadatke koji ne ovise jedni o drugima. Za daljnje čitanje o task grafu, pogledaj službenu Turborepo dokumentaciju za pokretanje tasks-a.

Remote cache je razlog zbog kojeg vrijedi platiti Vercel account (ili hostati vlastiti). Nakon turbo login i turbo link, CI pokretanja koja hitne cache preskaču kompajliranje i skidaju rezultat s CDN-a. Kod nas se lint task na 200 fajlova skratio s 45 sekundi na 2 sekunde na cache hitu. pnpm workspaces u kombinaciji s Turborepo cache ključem, koji hasira samo relevantne inpute, stvarno mijenja igru za timove od 10+ ljudi.

Česti problemi i rješenja

"Unable to resolve module react" na iOS-u nakon dodavanja paketa

Skoro uvijek Metro cache. Pokreni pnpm --filter mobile expo start --clear. Ako ne pomogne, obriši node_modules i pnpm store prune, zatim ponovna instalacija. Provjeri watchFolders u metro.config.js.

Dvije verzije Reacta u bundle-u

Pokreni pnpm ls react --depth Infinity u rootu. Ako se pojavi više instanci, dodaj react u public-hoist-pattern u .npmrc ili force resolucija preko pnpm.overrides u root package.json.

Sporo Gradle buildanje na CI

Cachiraj ~/.gradle/caches u Turborepo build tasku ili preko GitHub Actions cache akcije. Kombiniraj s Turborepo remote cacheom koji sprema Metro bundle rezultat.

EAS Build ne vidi dijeljeni paket

Provjeri da .easignore ne isključuje packages/ i da eas-build-pre-install hook cd-a na root i pokreće pnpm install. EAS log će uvijek pokazati koji folder je uzet kao root.

Česta pitanja

Trebam li koristiti monorepo za jednu React Native aplikaciju?

Ne. Ako imaš samo jedan mobilni klijent bez planiranog weba ili druge aplikacije, obični Expo projekt s pnpm-om je dovoljan. Monorepo se isplati čim postoji drugi klijent, dijeljena poslovna logika ili više timova koji dodiruju iste tipove.

Podržava li Expo pnpm workspaces u 2026?

Da, službeno od SDK 50 uz node-linker=hoisted. Zadana pnpm isolated strategija još uvijek stvara probleme s autolinkerom, pa je hoisted mod praktički obavezan za bare i Expo Prebuild setupove.

Koliko brzo Turborepo remote cache štedi CI vrijeme?

U mom timu s dvije aplikacije i pet paketa, remote cache je smanjio prosječan CI run s 12 na 3 minute na cache hitovima. Ušteda ovisi o veličini task grafa i koliko su izmjene lokalizirane. Više paketa znači više prilika za cache hit.

Mogu li dijeliti React Native komponente s Next.js webom?

Da, kroz react-native-web. Postavi UI paket koji koristi samo React Native primitive (View, Text, Pressable), i u Next.js aplikaciji dodaj transpilePackages: ["@my-platform/ui"]. Solito ili Expo Router web mod olakšavaju rutiranje između njih.

Kako verzionirati pakete u monorepou?

Ako paketi ne idu na npm, verzija je nevažna, sve je locked na commit. Ako paketi izlaze javno, koristi Changesets (@changesets/cli) koji dodaje changelog i bump automation kroz PR-ove. Turborepo je službeno kompatibilan s Changesets pipeline-om.

Yelena Petrov
O Autoru Yelena Petrov

React Native architect at a fintech. Builds platform teams, type-safe bridges, and runs the upgrade playbook so others don't have to.