AI React Native Expo Claude Code CLAUDE.md Mobile Development EAS Build

Claude Code for React Native & Expo: CLAUDE.md Templates, EAS Build CI/CD, and Expo Skills (2026)

The Prompt Shelf ·

React Native and Expo are a natural fit for Claude Code — the whole stack lives in a terminal-friendly JavaScript/TypeScript world, unlike native iOS or Android where Xcode and Android Studio gate a lot of the workflow. But that ease is deceptive. Expo Router, React Navigation, NativeWind versions, and native module permissions all have narrow, version-specific correct answers, and Claude Code will confidently generate the wrong one if your CLAUDE.md doesn’t pin them down.

This guide covers the full setup: a production CLAUDE.md template, the official Expo Skills and MCP Server integration that most guides on this topic don’t mention, EAS Build CI/CD wired through GitHub Actions, native module permission patterns, and the mistakes that show up most often in React Native projects using Claude Code. Real community CLAUDE.md and .cursorrules files for React Native/Expo are browsable in our gallery.

Why React Native Needs an Explicit CLAUDE.md

Two React Native projects can look identical from the file tree and still use completely incompatible patterns underneath. Without ground truth in CLAUDE.md, Claude Code has to guess, and it guesses based on whatever pattern is statistically common in its training data — which is often the older pattern, not the one your project actually uses.

The three guesses that go wrong most often:

  • Navigation library. Expo Router (file-based, like Next.js App Router) and React Navigation (imperative, NavigationContainer + stack/tab navigators) are structurally different. Claude Code will mix useRouter() calls with @react-navigation/native imports in the same file if you don’t state which one the project uses.
  • Styling system. StyleSheet.create(), NativeWind (Tailwind for RN), and Styled Components each imply different component patterns. NativeWind in particular has breaking changes between v2 and v4 — the class syntax and config shape differ enough that generated code from the wrong version won’t compile.
  • State management. Redux, Zustand, Jotai, and React Context + useReducer are all common in different generations of React Native projects. Claude will default to whichever one appears most in similar-looking codebases, not the one you’re actually using.

None of this is a Claude Code weakness specifically — every AI coding tool has the same problem. The fix is the same one that works for any framework: write down the actual stack, the actual folder structure, and the actual conventions, and treat CLAUDE.md as executable documentation rather than a wishlist.

Complete CLAUDE.md Template for React Native / Expo Projects

Here’s a production-ready starting point. Delete sections you don’t need — a shorter CLAUDE.md that’s 100% accurate outperforms a long one with stale assumptions.

# [AppName] — React Native / Expo App

## Project Overview
- Expo SDK 54, React Native 0.81, TypeScript strict mode
- Navigation: Expo Router v5 (file-based routing in `app/`)
- Styling: NativeWind v4 (Tailwind classes on RN components)
- State: Zustand for client state, TanStack Query v5 for server state
- Backend: [Supabase / custom REST API / etc.]
- Build: EAS Build (managed workflow, no native folders committed)

## Commands
- Start dev server: `npx expo start`
- Type check: `npx tsc --noEmit`
- Lint: `npx eslint . --ext .ts,.tsx`
- Test: `npx jest`
- Build (dev client, iOS): `eas build --profile development --platform ios`
- Build (production, both): `eas build --profile production --platform all`
- Submit: `eas submit --platform ios`

## Navigation Rules (Expo Router)
- File-based routing only — routes are files under `app/`
- NEVER import from `@react-navigation/native` directly; Expo Router wraps it
- Use `useRouter()` for programmatic navigation, `<Link>` for declarative
- Route params: `useLocalSearchParams<{ id: string }>()` — always type the params
- Route groups `(tabs)`, `(auth)` organize without affecting the URL path
- `_layout.tsx` defines shared UI/navigators for a route segment — one per directory that needs it

## Project Structure
app/                  # Expo Router routes (file = route)
  (tabs)/             # Tab navigation group
  (auth)/             # Auth flow group, no tab bar
  _layout.tsx          # Root layout — providers, fonts, splash screen
components/           # Shared, reusable components (PascalCase files)
features/<name>/      # Feature-scoped components, hooks, stores
hooks/                # Shared custom hooks (usePrefix)
lib/                  # API clients, utilities, non-React logic
stores/               # Zustand stores
assets/                # Images, fonts

## Styling (NativeWind v4)
- Tailwind classes via `className` prop — no StyleSheet.create() for new components
- Config lives in `tailwind.config.js` — check it before assuming a color/spacing token exists
- Platform-specific styles: `ios:` and `android:` variant prefixes, not `Platform.OS` branches, when a Tailwind variant covers it
- Dark mode via NativeWind's `dark:` variant, driven by `useColorScheme()`

## State Management
- Zustand for global client state — one store per domain, not one giant store
- TanStack Query for anything from the network — do not duplicate server data into Zustand
- AsyncStorage for non-sensitive persisted state; `expo-secure-store` for tokens/credentials
- No React Context for frequently-updating state (causes unnecessary re-renders down the tree)

## Native Modules & Permissions
- Every native module touching hardware (camera, location, notifications, contacts) requires:
  1. The corresponding `expo-*` package (not a bare React Native native module, unless there's no Expo equivalent)
  2. A permission request via the module's `requestPermissionsAsync()` before first use
  3. An `app.json` / `app.config.ts` entry under `plugins` with the permission description strings
- Never call a native API without checking permission status first — always branch on `granted`
- Test permission flows on a real device or EAS dev client — the standard Expo Go client cannot include all native modules

## TypeScript Conventions
- Strict mode, no `any` — use `unknown` and narrow, or define the real type
- Interfaces for component props, `type` for unions/utility types
- Named exports for shared components; default export only for route files (Expo Router requires this)
- Path aliases: `@/` maps to project root (configured in `tsconfig.json` and `babel.config.js`)

## Performance Rules
- `FlashList` (not `FlatList`) for any list that can exceed ~20 items
- `expo-image` (not `Image` from react-native) for all remote/asset images — built-in caching and better memory behavior
- Reanimated worklets for animations — never drive animation state through `useState` + `setInterval`
- Memoize list item renderers with `React.memo()`; keep `keyExtractor` stable and unique

## Testing
- Unit/component tests: Jest + `@testing-library/react-native`
- E2E: Maestro (YAML-based flows) — flows live in `.maestro/`
- Mock native modules in `jest.setup.js`, not per-test-file
- Do not write snapshot tests for components with animation or date-dependent output

## What NOT to Do
- Do not mix Expo Router and React Navigation in the same app
- Do not use `Platform.OS` checks where a NativeWind `ios:`/`android:` variant already covers it
- Do not add a bare (non-Expo) native module without checking it supports the current Expo SDK's New Architecture requirement
- Do not commit `ios/` or `android/` folders unless the project has explicitly ejected from managed workflow

Expo Skills and the Expo MCP Server (Most Guides Skip This)

Most Claude Code + React Native guides stop at the CLAUDE.md template above. As of the current Expo SDK release cycle, there’s an official integration layer that goes further: Expo Skills and the Expo MCP Server, both distributed through the Claude Code plugin marketplace.

Setup, run from your project root:

# install the official Expo plugin (skills + MCP server)
claude plugin install expo@claude-plugins-official

This does two things:

  1. Expo Skills — packaged instructions that teach Claude Code Expo-specific conventions (SDK version handling, EAS profiles, Expo Router file conventions) without you having to write them all into CLAUDE.md by hand. They activate automatically when Claude Code detects relevant work.
  2. Expo MCP Server — gives Claude Code live access to Expo documentation and your EAS project state (build status, submission status) instead of relying on training data that may reference an outdated SDK version.

Verify the plugin is working by asking Claude Code directly: “Open package.json and tell me which Expo SDK version this project targets.” If the plugin is wired up correctly, it cross-checks the answer against current Expo docs instead of guessing from a possibly stale internal model of SDK versions.

This matters more than it sounds. Expo ships SDK upgrades roughly every quarter, and each one changes API surface — expo-camera’s API alone changed shape twice in recent SDKs. A model with no live doc access will confidently write code for the SDK version it remembers best, not the one in your package.json.

EAS Build CI/CD: Wiring Claude Code Into Your Pipeline

This is the section most React Native + Claude Code guides skip entirely, or mention only in passing. If you want Claude Code to be useful beyond local npx expo start iteration, it needs a build pipeline it can reason about and trigger.

eas.json build profiles, the foundation everything else depends on:

{
  "build": {
    "development": {
      "developmentClient": true,
      "distribution": "internal",
      "ios": { "simulator": true }
    },
    "preview": {
      "distribution": "internal",
      "channel": "preview"
    },
    "production": {
      "autoIncrement": true,
      "channel": "production"
    }
  },
  "submit": {
    "production": {}
  }
}

GitHub Actions workflow that runs type-checks and lint before triggering an EAS build — put this in .github/workflows/eas-build.yml:

name: EAS Build
on:
  push:
    branches: [main]
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
      - run: npm ci
      - run: npx tsc --noEmit
      - run: npx eslint . --ext .ts,.tsx
      - uses: expo/expo-github-action@v9
        with:
          eas-version: latest
          token: ${{ secrets.EXPO_TOKEN }}
      - run: eas build --platform all --profile production --non-interactive

Add this to CLAUDE.md so Claude Code treats the pipeline as ground truth rather than proposing ad-hoc build commands:

## CI/CD
- All builds run through EAS Build via GitHub Actions (`.github/workflows/eas-build.yml`)
- Never suggest `eas build` without `--non-interactive` in CI contexts
- Production builds auto-increment build number (see `eas.json``autoIncrement`)
- Type-check and lint MUST pass before a build is triggered — do not skip these to "save time"
- EAS Update (OTA) for JS-only changes: `eas update --branch production --message "..."` — no new build required for non-native changes

That last line matters for a workflow reason: Claude Code left to its own judgment will often propose a full EAS Build for a change that’s actually JS-only and could ship via EAS Update in under a minute, without an app store review cycle.

Native Module & Permission Patterns

Every native capability in Expo — camera, location, notifications, contacts, biometrics — needs an explicit permission flow, and Claude Code generates inconsistent permission-handling code without a stated pattern to follow. Here’s a template pattern to drop into CLAUDE.md:

## Permission Request Pattern
Every native module hook follows this shape:

```typescript
import * as Camera from 'expo-camera';

function useCameraPermission() {
  const [permission, requestPermission] = Camera.useCameraPermissions();

  const ensurePermission = async (): Promise<boolean> => {
    if (permission?.granted) return true;
    const result = await requestPermission();
    return result.granted;
  };

  return { permission, ensurePermission };
}

Rules:

  • Check permission?.granted before every use of the native API, not just on mount
  • Show a rationale UI before the OS permission dialog for anything beyond notifications
  • Handle the “denied and can’t ask again” state — link to Linking.openSettings()
  • Never assume a permission persists across app updates on Android

And the `app.json` side — permissions that aren't declared here will fail silently or get rejected at App Store review, not at build time:

```json
{
  "expo": {
    "plugins": [
      [
        "expo-camera",
        {
          "cameraPermission": "Allow $(PRODUCT_NAME) to access your camera to scan receipts."
        }
      ],
      [
        "expo-location",
        {
          "locationAlwaysAndWhenInUsePermission": "Allow $(PRODUCT_NAME) to use your location to find nearby stores."
        }
      ]
    ]
  }
}

The permission description strings are not boilerplate — Apple’s App Store review rejects vague ones (“uses your location”) and expects a concrete reason tied to a feature. Write real ones from the start rather than placeholder text that has to be fixed during a review rejection cycle.

AGENTS.md Version for Cross-Tool Teams

If your team mixes Claude Code with Cursor or GitHub Copilot, an AGENTS.md at the project root keeps all tools reading the same ground truth:

# [AppName] — AI Agent Context

## Stack
Expo SDK 54, React Native 0.81, TypeScript strict, Expo Router v5, NativeWind v4, Zustand, TanStack Query v5

## Commands
- Dev: `npx expo start`
- Type check: `npx tsc --noEmit`
- Test: `npx jest`
- Build: `eas build --profile production --platform all`

## Navigation
Expo Router only (file-based, `app/` directory). Never `@react-navigation/native` directly.

## Styling
NativeWind v4 via `className`. No StyleSheet.create() for new components.

## State
Zustand (client) + TanStack Query (server). No Redux, no Context for frequently-updating state.

## Performance
FlashList over FlatList. expo-image over Image. Reanimated worklets for animation.

## Known Pitfalls
- SDK version ambiguity — always check package.json before assuming API shape
- Do not mix navigation libraries
- Every native module requires a permission check before use

Common Mistakes We See in React Native + Claude Code Projects

Mixing Expo Router and React Navigation. This is the single most common failure mode. It usually starts when a developer copies a React Navigation snippet from an older tutorial into an Expo Router project. Once both exist in the dependency tree, Claude Code has two valid-looking patterns to choose from and picks inconsistently across sessions.

NativeWind version drift. v2 used nativewind/babel differently than v4’s compiler-based approach, and the class syntax for some utilities changed. If your CLAUDE.md doesn’t state the exact version, Claude Code may generate v2-style config for a v4 project (or vice versa) — this fails at build time with an unhelpful error, not a clear version mismatch message.

FlatList for large lists. Claude Code defaults to FlatList because it’s the more commonly seen pattern in training data, even in fresh 2026 projects. For any list that can realistically grow past 20-30 items, FlashList is a meaningfully different performance profile — state this explicitly rather than relying on Claude to know your app’s list will eventually have hundreds of rows.

Skipping the permission rationale step. Generated permission flows often call requestPermissionsAsync() directly on mount, which produces a system dialog with no context the first time a user opens a screen. iOS and Android app review guidelines both penalize this pattern; a rationale screen or inline explanation before the system prompt fixes it.

Forgetting EAS Update vs. EAS Build. For JS-only changes (bug fixes, copy changes, most feature work that doesn’t touch native code), eas update ships instantly without app store review. Without an explicit rule, Claude Code may propose a full rebuild-and-resubmit cycle for a one-line text change.

Frequently Asked Questions

Can Claude Code work directly with the Expo Go app for testing?

Yes, for anything that doesn’t require a custom native module. npx expo start and scanning the QR code with Expo Go gives Claude Code (via terminal output and your reports back) a fast iteration loop. Once your project includes any native module without an Expo Go equivalent, you need a development build (eas build --profile development) instead — Claude Code can trigger the build, but you’ll install and test on the resulting dev client, not Expo Go.

Does Claude Code understand Expo’s New Architecture (Fabric/TurboModules)?

It understands the concept and can reason about compatibility issues when you report an error, but it won’t reliably know whether a specific third-party native module supports the New Architecture without checking. State your New Architecture status (newArchEnabled in app.json) explicitly in CLAUDE.md, and treat any proposed native module addition as something to verify against the module’s own compatibility docs before installing.

Should CLAUDE.md or AGENTS.md come first in a React Native project?

If you’re only using Claude Code, CLAUDE.md alone is sufficient — no need to duplicate into AGENTS.md. If your team uses multiple AI tools, put the framework-agnostic stack facts (navigation library, state management, styling system, commands) in AGENTS.md, and keep Claude-specific preferences (how verbose to be, commit message conventions, permission settings) in CLAUDE.md.

What about Detox instead of Maestro for E2E testing?

Both work; the guide above defaults to Maestro because its YAML-based flow files are easier for Claude Code to generate and modify correctly without deep native testing infrastructure knowledge. Detox is more powerful for deep native assertions but requires more setup (native build configuration) that Claude Code can’t fully automate from the terminal. If your project already uses Detox, state that explicitly — otherwise Claude Code may propose Maestro flows that don’t fit your existing test infrastructure.


Real React Native and Expo CLAUDE.md/.cursorrules examples — including community-contributed ones with active GitHub stars — are browsable in our gallery. For related setup patterns, see our guides on Claude Code for Flutter and Claude Code for Swift/iOS.

Related Articles

Explore the collection

Browse all AI coding rules — CLAUDE.md, .cursorrules, AGENTS.md, and more.

Browse Rules