BoilerplateHub

Claude Code Setup for Expo

Rules file: CLAUDE.md

Expo's value comes from managing the native layer for you, and almost every agent failure here is an agent stepping around that management because older React Native advice told it to. The agent needs to know the SDK version, whether the project uses continuous native generation or has committed native directories, and whether the target runtime is Expo Go or a development build. Package versions are pinned to the SDK, so installing with the wrong tool is enough to break a build that was previously fine.

Configuring Claude Code for Expo

  • Record the Expo SDK version and whether the project is prebuild managed or has committed native directories, in that order.
  • State the test runtime, Expo Go or a development build, since it changes what the agent can suggest as a verification step.
  • List your EAS build profiles so the agent references a real profile name rather than inventing one.

What to put in CLAUDE.md

The content is the same across agents, only the filename differs. Copy this, adapt the commands to your repository, and save it as CLAUDE.md.

CLAUDE.md
Download
# AGENTS.md

This is a cross platform mobile app built with Expo, Expo Router, and TypeScript.

## Project

Stack: Expo SDK 52, React Native, TypeScript (strict), Expo Router, React Query, Zustand for local state, EAS Build and EAS Update.

Layout:

- `app/` Expo Router routes, the directory tree is the navigation tree
- `app/(tabs)/` bottom tab navigator, `app/(auth)/` unauthenticated stack
- `app/_layout.tsx` root providers, `app/+not-found.tsx` fallback
- `src/components/` shared presentational components
- `src/features/<feature>/` screens, hooks, and API calls for one feature
- `src/api/` typed fetch clients and React Query hooks
- `src/native/` thin wrappers around Expo modules (camera, notifications, storage)
- `src/theme/` spacing, colors, typography tokens
- `app.config.ts` app identity, plugins, permissions
- `eas.json` build profiles

Entry points: `app/_layout.tsx`, `app.config.ts`, `eas.json`.

## Commands

```bash
pnpm install
pnpm start                       # metro bundler
pnpm ios                         # run on iOS simulator
pnpm android                     # run on Android emulator
pnpm lint
pnpm typecheck                   # tsc --noEmit
pnpm test                        # jest with jest-expo
npx expo install <package>       # install a package at the SDK compatible version
npx expo prebuild --clean        # regenerate native projects, ask first
npx expo-doctor                  # diagnose config and version drift
eas build --profile development --platform ios
eas build --profile production --platform all
eas update --branch preview      # OTA update, JS only
```

Use `npx expo install` for any package with a native component, not `pnpm add`.

A change to `app.config.ts` plugins, permissions, or a new native dependency requires a new development build. JavaScript only changes reload in the existing build.

## Platform notes

- Test every screen on both an iOS simulator and an Android emulator before calling it done.
- Safe areas: use `useSafeAreaInsets` from react-native-safe-area-context. Do not hardcode status bar height.
- Android back button needs explicit handling on any screen with a modal or a multi step flow.
- Permissions are requested at the moment of use, never on app launch. Both platforms need a usage description in `app.config.ts`.
- Keyboard behavior differs: use `KeyboardAvoidingView` with `behavior="padding"` on iOS and `"height"` on Android.
- Shadows need `elevation` on Android and `shadow*` props on iOS. Use the helper in `src/theme/shadows.ts`.
- Fonts and icons load asynchronously, keep the splash screen up until `useFonts` resolves.
- OTA updates cannot ship native changes. If a change touches native code it needs a store build.

## Code style

- Styles go in `StyleSheet.create` at the bottom of the file. No inline style objects inside render.
- Use `FlashList` or `FlatList` for any list that can exceed ten items. Never map an array into a ScrollView.
- Every list item component is memoized and every list has a stable `keyExtractor`.
- Server state belongs to React Query. Local UI state belongs to component state or Zustand. Do not mirror server data in a store.
- Navigation uses typed routes from Expo Router. No string concatenation for hrefs.
- Native APIs are only called through `src/native/`, so permissions and fallbacks live in one place.
- Spacing and color come from `src/theme/`. No raw hex values or magic numbers in components.
- Images use `expo-image` with an explicit width and height.

## Boundaries

Do not touch without explicit instruction:

- `ios/` and `android/` directories. They are generated by prebuild.
- `app.config.ts` bundle identifier, package name, scheme, version, or build number.
- `eas.json` build profiles and any signing credential.
- `pnpm-lock.yaml`, `.env`, store metadata and screenshots.

Needs human review: new native dependencies, permission additions, changes to push notification handling, and anything affecting deep links.

Version numbers are set by the release process, not by a code change.

## Testing

- `pnpm test` runs Jest with the jest-expo preset.
- Required tests: everything in `src/api/` (request shaping, response parsing), pure helpers, and state reducers.
- Component tests use @testing-library/react-native and cover behavior, not layout.
- Native module wrappers are tested with the Expo module mocked.
- Interaction and layout are verified on device, not in the test suite.

## Git workflow

- Branch from `main`: `feat/short-description`, `fix/short-description`.
- Conventional commits: `fix(onboarding): keep keyboard clear of the submit button`.
- PR description includes screenshots or a screen recording from both iOS and Android.
- State in the PR whether the change is OTA safe or requires a new build.
- Never commit to `main`.

Rule these out for Expo

These are the failures that repeat across sessions, so each one belongs in CLAUDE.md.

Editing ios/ and android/ in a prebuild managed project

Asked to change an app permission or a native setting, an agent opens Info.plist or AndroidManifest.xml and edits it directly. If the project uses continuous native generation, the next prebuild regenerates those directories and silently discards the change, so the bug reappears after a clean build. Add to your rules file: 'Native configuration is expressed in app.json or app.config.js, through config plugins. Never edit files under ios/ or android/, and never commit them.'

Using npm install for SDK-managed packages

Agents run npm install expo-camera and get the latest release, which is frequently incompatible with the installed SDK and fails at build time with an error that points nowhere useful. The expo install command resolves the version the SDK actually expects. Write: 'Install Expo packages with npx expo install, never npm or yarn add. Run npx expo install --check after any dependency change and fix what it reports.'

Recommending react-native link and manual native setup

Library READMEs and older training data still describe react-native link, manual Podfile edits, and MainApplication.java registration. None of that applies here, and following it in a managed project produces changes that either fail or get regenerated away. Add: 'Native setup is done with config plugins. react-native link does not exist, and manual Podfile or Gradle instructions from a library README must be translated into a plugin or flagged rather than applied.'

Assuming Expo Go can run any native module

Agents suggest testing in Expo Go after adding a library with custom native code, which Expo Go cannot load because its native binary is fixed. The resulting error looks like a broken install and sends the agent debugging JavaScript. State the runtime: 'This project runs on a development build, not Expo Go. Any library with custom native code requires a new development build before it can be tested.'

Mixing manual navigators with the Expo Router file tree

In an Expo Router project, agents add a manually constructed stack navigator inside a screen, or create a route file without the _layout.tsx that gives it a parent, producing routes that render outside the intended navigation hierarchy. Add: 'Routing is file based under app/. Screens are files, groups use parentheses, and every directory that needs a navigator has a _layout.tsx. Do not construct navigators by hand.'

Treating app config extra values as private

Agents put an API key into the extra block of app.json or into a non-public EAS environment variable and treat it as a secret, but anything bundled into the app ships to every device and can be extracted. Only build-time credentials that never enter the bundle are actually protected. Write: 'Nothing in app config or in the JavaScript bundle is secret. Signing credentials and service tokens live in EAS secrets, and any key that must stay private is used from your backend, never from the app.'

Skills that pair with this setup

Same framework, other agents