Architect-level guidance for React Native apps — Expo vs bare workflow, New Architecture (Fabric/TurboModules), navigation (Expo Router / React Navigation), state and offline-first data, native module strategy, monorepo sharing with web, theming, and release engineering with EAS.
When agents use itUse this whenever the user is starting or restructuring a React Native / Expo project, choosing libraries for navigation, storage, or networking, deciding whether to write a native module, asking about code sharing between React Native and React web, or asking how to organise a mobile codebase. Also apply it when reviewing React Native code for architectural issues.
Install
Copilot (VS Code, Visual Studio, Copilot CLI and github.com) reads skills from the repository — commit them so the whole team gets them.
npx skills add AGCO-Global/org-skills --skill react-native-architecture -a github-copilot
# or with the org installer (adds .github/skills/react-native-architecture):
npx -y github:AGCO-Global/org-skills add skill react-native-architecture
Uses the open skills CLI. Works with Claude Code, Codex, Cursor, Copilot, Gemini CLI, OpenCode, Windsurf and 60+ others — it asks which agent to install into.
Installs the frontend-react-native-skills plugin, which bundles all Frontend / React Native skills and keeps them updated.
/plugin marketplace add AGCO-Global/org-skills
/plugin install frontend-react-native-skills@org-skills
# or just this skill, in this repository:
npx skills add AGCO-Global/org-skills --skill react-native-architecture -a claude-code
Use Download .zip above, then upload it under Customize → Skills → Upload skill. Team and Enterprise admins can sync this repo as a plugin marketplace instead.
Installs into .agents/skills/, which Codex reads.
npx skills add AGCO-Global/org-skills --skill react-native-architecture -a codex
Installs into Cursor's skills folder.
npx skills add AGCO-Global/org-skills --skill react-native-architecture -a cursor
Installs into Gemini CLI's skills folder.
npx skills add AGCO-Global/org-skills --skill react-native-architecture -a gemini-cli
Any tool with rules, instructions or custom prompts: use Copy SKILL.md above and paste it in. It is plain Markdown.
Commands use your normal git sign-in to GitHub, so they work while the repository is private. Node.js 20+ required.
Skill contents
React Native Architecture
Mobile is a different discipline from web: binaries ship through app stores, users are offline, devices are slow, and mistakes take a week to roll back. Design for that.
1. Platform decisions (write them into ARCHITECTURE.md)
Decision
Default
Change when
Workflow
Expo (managed, with expo-dev-client and config plugins)
You must ship a native SDK with no config plugin and can't write one → bare, but keep expo modules. "Ejecting" is rarely justified now.
Architecture
New Architecture enabled (Fabric + TurboModules, bridgeless)
Only a legacy dependency blocks it — then plan the migration, don't disable forever.
JS engine
Hermes
Never Jsc.
Navigation
Expo Router (file-based, deep links for free)
Existing large React Navigation codebase → stay on React Navigation v7.
Styling
StyleSheet + a token file, or NativeWind if the team is Tailwind-fluent
Design system requires runtime theming across many brands → Tamagui / Unistyles.
Language
TypeScript strict
Never.
Package manager
pnpm with node-linker=hoisted (RN tooling needs hoisting)
Yarn in existing monorepo.
Builds/updates
EAS Build + EAS Update
Enterprise CI requirement → Fastlane + self-hosted, still with Expo modules.
2. Folder structure
Same feature-first principle as React web, with mobile-specific additions:
src/
├── app/ # Expo Router routes only — thin files that render feature screens
│ ├── (auth)/
│ ├── (tabs)/
│ └── _layout.tsx
├── features/<feature>/ # screens/, components/, hooks/, api/, model/, index.ts
├── shared/
│ ├── ui/ # primitives; every one supports light/dark and dynamic type
│ ├── lib/ # api client, storage, analytics, logger, permissions
│ ├── hooks/
│ └── theme/ # tokens: colors, spacing, typography, radii
├── native/ # local Expo modules / TurboModules (Swift/Kotlin) with TS bindings
└── assets/
Rules:
Route files in app/ contain no business logic; they import a screen from a feature. Keeps routes testable and the router swappable.
Platform forks via Component.ios.tsx / Component.android.tsx only for real divergence; prefer Platform.select inside one file for small differences.
shared/ui never imports from features/.
3. Navigation
Group routes by auth state: (auth) and (app) groups with a root _layout.tsx that redirects based on session.
Typed routes on (experiments.typedRoutes) so router.push params are checked.
Deep links and universal links configured from day one (scheme, associatedDomains, intentFilters). Retrofitting is painful.
Modals as route groups ((modals)), not component state — they need to be deep-linkable and back-button aware.
Keep tab bars to ≤ 5; nest stacks inside tabs, not tabs inside stacks.
4. State and data — offline first
Mobile networks fail constantly. The architecture must assume it:
Kind
Tool
Server data
TanStack Query with persistQueryClient to MMKV and networkMode: 'offlineFirst'
Mutations while offline
Query mutation queue + onlineManager; or a dedicated outbox table for critical writes
Key-value (tokens, settings)
MMKV (react-native-mmkv) — synchronous, fast. Never AsyncStorage for hot paths.
Secrets
expo-secure-store (Keychain / Keystore) — tokens go here, not MMKV
Relational / large local data
Expo SQLite (with Drizzle) or WatermelonDB for sync-heavy apps
Client state
Zustand with MMKV persistence
Forms
React Hook Form + Zod
Design the sync story explicitly: last-write-wins vs server-authoritative vs CRDT. Document it. Show the user the conflict case before they pick.
5. Native modules — when and how
Decide in this order:
Does an Expo SDK module exist? Use it (expo-camera, expo-location, expo-notifications…). They have config plugins and New Architecture support.
Does a well-maintained community module exist with a config plugin? Check New Architecture support, last release date, open issues.
Write a local Expo Module (npx create-expo-module --local) in Swift/Kotlin. Small, typed, no bridge boilerplate. This is the default for custom native code.
TurboModule / Fabric component by hand only for performance-critical rendering or when integrating an existing large native codebase.
Never patch node_modules without patch-package and a tracking issue. Never run expo prebuild output into git unless you have fully committed to the bare workflow.
6. Sharing code with web
Monorepo (Turborepo/Nx): apps/mobile, apps/web, packages/core (domain logic, API client, Zod schemas, query hooks), packages/ui (platform-agnostic primitives only if genuinely shared).
Share logic, not screens. Attempts to share full UI via react-native-web succeed for simple apps and fight you for complex ones; be honest about that.
packages/core has no react-native imports. Enforce with ESLint.
7. Theming, accessibility, i18n — built in, not bolted on
Tokens in shared/theme; components read via a useTheme() hook; support useColorScheme() from day one.
Respect Dynamic Type: use allowFontScaling defaults, test at 200%.
Every touchable has accessibilityRole, accessibilityLabel; hit targets ≥ 44×44.
i18n via i18next + expo-localization; no hard-coded strings in screens; RTL tested with I18nManager.forceRTL in dev.
8. Observability and quality gates
Crash reporting (Sentry) with source maps uploaded per build; wrap the root in an error boundary that shows a designed screen and offers "reload".
Performance monitoring on cold start, screen TTI, and JS thread FPS (see react-native-performance skill).
Feature flags from a remote config so risky features can be killed without a store release.
expo-doctor, tsc, ESLint (eslint-plugin-react-native, eslint-plugin-react-hooks), and Jest run in CI on every PR; EAS Build preview per PR.
9. Release engineering
Semantic versioning for version; auto-increment buildNumber/versionCode in EAS.
Channels: development (dev client), preview (internal testers), production. EAS Update for JS-only fixes; native changes require a store build — teach the team which is which.
Runtime version policy appVersion so updates never load against an incompatible native binary.
Staged rollout (Play Console %) and phased release (App Store) always on for production.
Keep a RELEASE.md runbook: who bumps, how to roll back an update (eas update --republish), how to hotfix.
Anti-patterns to flag
AsyncStorage for tokens or hot reads.
Business logic in route files or in _layout.tsx.
Dimensions.get('window') at module scope (breaks rotation/foldables); use useWindowDimensions.
Disabling the New Architecture "for now" without a dated migration ticket.
Fetching without offline handling and calling it done.
Copying web components into RN and wrapping them in View without redesigning for touch.
---
name: react-native-architecture
description: Architect-level guidance for React Native apps — Expo vs bare workflow, New Architecture (Fabric/TurboModules), navigation (Expo Router / React Navigation), state and offline-first data, native module strategy, monorepo sharing with web, theming, and release engineering with EAS. Use this whenever the user is starting or restructuring a React Native / Expo project, choosing libraries for navigation, storage, or networking, deciding whether to write a native module, asking about code sharing between React Native and React web, or asking how to organise a mobile codebase. Also apply it when reviewing React Native code for architectural issues.
metadata:
technology: React Native
type: architecture
---
# React Native Architecture
Mobile is a different discipline from web: binaries ship through app stores, users are offline, devices are slow, and mistakes take a week to roll back. Design for that.
## 1. Platform decisions (write them into ARCHITECTURE.md)
| Decision | Default | Change when |
|---|---|---|
| Workflow | **Expo (managed, with `expo-dev-client` and config plugins)** | You must ship a native SDK with no config plugin and can't write one → bare, but keep `expo` modules. "Ejecting" is rarely justified now. |
| Architecture | **New Architecture enabled** (Fabric + TurboModules, bridgeless) | Only a legacy dependency blocks it — then plan the migration, don't disable forever. |
| JS engine | **Hermes** | Never Jsc. |
| Navigation | **Expo Router** (file-based, deep links for free) | Existing large React Navigation codebase → stay on React Navigation v7. |
| Styling | **StyleSheet + a token file**, or **NativeWind** if the team is Tailwind-fluent | Design system requires runtime theming across many brands → Tamagui / Unistyles. |
| Language | TypeScript strict | Never. |
| Package manager | pnpm with `node-linker=hoisted` (RN tooling needs hoisting) | Yarn in existing monorepo. |
| Builds/updates | **EAS Build + EAS Update** | Enterprise CI requirement → Fastlane + self-hosted, still with Expo modules. |
## 2. Folder structure
Same feature-first principle as React web, with mobile-specific additions:
```
src/
├── app/ # Expo Router routes only — thin files that render feature screens
│ ├── (auth)/
│ ├── (tabs)/
│ └── _layout.tsx
├── features/<feature>/ # screens/, components/, hooks/, api/, model/, index.ts
├── shared/
│ ├── ui/ # primitives; every one supports light/dark and dynamic type
│ ├── lib/ # api client, storage, analytics, logger, permissions
│ ├── hooks/
│ └── theme/ # tokens: colors, spacing, typography, radii
├── native/ # local Expo modules / TurboModules (Swift/Kotlin) with TS bindings
└── assets/
```
Rules:
- Route files in `app/` contain no business logic; they import a screen from a feature. Keeps routes testable and the router swappable.
- Platform forks via `Component.ios.tsx` / `Component.android.tsx` only for real divergence; prefer `Platform.select` inside one file for small differences.
- `shared/ui` never imports from `features/`.
## 3. Navigation
- Group routes by auth state: `(auth)` and `(app)` groups with a root `_layout.tsx` that redirects based on session.
- Typed routes on (`experiments.typedRoutes`) so `router.push` params are checked.
- Deep links and universal links configured from day one (`scheme`, `associatedDomains`, `intentFilters`). Retrofitting is painful.
- Modals as route groups (`(modals)`), not component state — they need to be deep-linkable and back-button aware.
- Keep tab bars to ≤ 5; nest stacks inside tabs, not tabs inside stacks.
## 4. State and data — offline first
Mobile networks fail constantly. The architecture must assume it:
| Kind | Tool |
|---|---|
| Server data | **TanStack Query** with `persistQueryClient` to MMKV and `networkMode: 'offlineFirst'` |
| Mutations while offline | Query mutation queue + `onlineManager`; or a dedicated outbox table for critical writes |
| Key-value (tokens, settings) | **MMKV** (`react-native-mmkv`) — synchronous, fast. Never AsyncStorage for hot paths. |
| Secrets | `expo-secure-store` (Keychain / Keystore) — tokens go here, not MMKV |
| Relational / large local data | **Expo SQLite** (with Drizzle) or **WatermelonDB** for sync-heavy apps |
| Client state | Zustand with MMKV persistence |
| Forms | React Hook Form + Zod |
Design the sync story explicitly: last-write-wins vs server-authoritative vs CRDT. Document it. Show the user the conflict case before they pick.
## 5. Native modules — when and how
Decide in this order:
1. **Does an Expo SDK module exist?** Use it (`expo-camera`, `expo-location`, `expo-notifications`…). They have config plugins and New Architecture support.
2. **Does a well-maintained community module exist with a config plugin?** Check New Architecture support, last release date, open issues.
3. **Write a local Expo Module** (`npx create-expo-module --local`) in Swift/Kotlin. Small, typed, no bridge boilerplate. This is the default for custom native code.
4. **TurboModule / Fabric component by hand** only for performance-critical rendering or when integrating an existing large native codebase.
Never patch `node_modules` without `patch-package` and a tracking issue. Never run `expo prebuild` output into git unless you have fully committed to the bare workflow.
## 6. Sharing code with web
- Monorepo (Turborepo/Nx): `apps/mobile`, `apps/web`, `packages/core` (domain logic, API client, Zod schemas, query hooks), `packages/ui` (platform-agnostic primitives only if genuinely shared).
- Share **logic**, not screens. Attempts to share full UI via `react-native-web` succeed for simple apps and fight you for complex ones; be honest about that.
- `packages/core` has no `react-native` imports. Enforce with ESLint.
## 7. Theming, accessibility, i18n — built in, not bolted on
- Tokens in `shared/theme`; components read via a `useTheme()` hook; support `useColorScheme()` from day one.
- Respect Dynamic Type: use `allowFontScaling` defaults, test at 200%.
- Every touchable has `accessibilityRole`, `accessibilityLabel`; hit targets ≥ 44×44.
- i18n via `i18next` + `expo-localization`; no hard-coded strings in screens; RTL tested with `I18nManager.forceRTL` in dev.
## 8. Observability and quality gates
- Crash reporting (Sentry) with source maps uploaded per build; wrap the root in an error boundary that shows a designed screen and offers "reload".
- Performance monitoring on cold start, screen TTI, and JS thread FPS (see `react-native-performance` skill).
- Feature flags from a remote config so risky features can be killed without a store release.
- `expo-doctor`, `tsc`, ESLint (`eslint-plugin-react-native`, `eslint-plugin-react-hooks`), and Jest run in CI on every PR; EAS Build preview per PR.
## 9. Release engineering
- Semantic versioning for `version`; auto-increment `buildNumber`/`versionCode` in EAS.
- Channels: `development` (dev client), `preview` (internal testers), `production`. EAS Update for JS-only fixes; native changes require a store build — teach the team which is which.
- Runtime version policy `appVersion` so updates never load against an incompatible native binary.
- Staged rollout (Play Console %) and phased release (App Store) always on for production.
- Keep a `RELEASE.md` runbook: who bumps, how to roll back an update (`eas update --republish`), how to hotfix.
## Anti-patterns to flag
- AsyncStorage for tokens or hot reads.
- Business logic in route files or in `_layout.tsx`.
- `Dimensions.get('window')` at module scope (breaks rotation/foldables); use `useWindowDimensions`.
- Inline `require('./img.png')` scattered everywhere — centralise assets.
- Disabling the New Architecture "for now" without a dated migration ticket.
- Fetching without offline handling and calling it done.
- Copying web components into RN and wrapping them in `View` without redesigning for touch.