Rule Writing

CLAUDE.md for Expo Projects

Expo is React Native with batteries included — AI generates bare RN patterns that break Expo's managed workflow. Rules for Expo Router, EAS, config plugins, and Expo SDK.

7 min read·December 15, 2025

Expo is React Native with batteries — AI generates bare RN patterns that break it

Expo Router, EAS Build, SDK modules, config plugins, and the managed workflow

Why Expo Needs Rules Beyond React Native Rules

Expo is React Native's most popular development platform — it handles builds, updates, and native module management so you don't need Xcode or Android Studio for most tasks. AI assistants generate bare React Native CLI patterns that break Expo's workflow: manual native module linking (Expo handles it automatically), react-native-cli commands (use npx expo instead), and manual Xcode/Gradle config (use config plugins).

Expo has evolved from a 'limited managed workflow' into a full-featured platform: Expo Router provides file-based navigation (like Next.js for mobile), EAS Build handles cloud builds, EAS Update provides over-the-air updates, and the Expo SDK provides polished native modules. AI that generates bare RN patterns misses all of these.

These rules target Expo SDK 51+ with Expo Router. If you're using Expo with React Navigation instead of Expo Router, adjust the navigation rules — but the build, SDK, and config plugin rules apply regardless.

Rule 1: Expo Router for File-Based Navigation

The rule: 'Use Expo Router for all navigation — file-based routing in the app/ directory. app/index.tsx is the home screen. app/profile.tsx is /profile. app/settings/notifications.tsx is /settings/notifications. Dynamic routes: app/user/[id].tsx. Layouts: app/_layout.tsx wraps all screens. Tab navigation: app/(tabs)/_layout.tsx with Tabs component.'

For navigation: 'Use <Link href="/profile"> for declarative navigation. Use router.push("/profile") for programmatic navigation. Use router.replace for replacing the current screen. Use useLocalSearchParams() for route params: const { id } = useLocalSearchParams<{ id: string }>(). Use typed routes with expo-router/types for type-safe navigation.'

AI generates React Navigation setup (createNativeStackNavigator, NavigationContainer) in Expo Router projects. Expo Router replaces all of this with file-system conventions — no navigation configuration code. One _layout.tsx replaces 50 lines of navigator setup.

  • File-based routing in app/ — index.tsx, [param].tsx, _layout.tsx
  • Tab layout: app/(tabs)/_layout.tsx with <Tabs> component
  • <Link href='/path'> for declarative — router.push() for programmatic
  • useLocalSearchParams() for typed route params
  • No createStackNavigator, no NavigationContainer — file system is the router
💡 Files Are Routes

app/profile.tsx = /profile screen. app/(tabs)/_layout.tsx = tab navigation. No createStackNavigator, no NavigationContainer. One _layout.tsx replaces 50 lines of navigator setup code.

Rule 2: Expo SDK Modules Over Community Packages

The rule: 'Use Expo SDK modules when available — they're tested, maintained, and work with Expo's build system. expo-camera instead of react-native-camera. expo-image instead of react-native-fast-image. expo-notifications instead of react-native-push-notification. expo-file-system instead of react-native-fs. expo-secure-store instead of react-native-keychain. Check the Expo SDK docs before installing any community package.'

For checking compatibility: 'Before installing any package, check: 1) Does Expo SDK have an equivalent? Use it. 2) Is the community package in Expo's known-compatible list? (npx expo install checks this). 3) Does it require native code changes? If yes, you need a config plugin or development build. Never run react-native link — Expo handles native linking automatically.'

AI installs community packages from React Native training data — many don't work with Expo or conflict with Expo's built-in modules. One rule ('check Expo SDK first') prevents broken builds and dependency conflicts.

⚠️ Expo SDK First

Before installing any community package: does Expo SDK have it? expo-camera, expo-image, expo-notifications, expo-file-system cover most needs. Community packages may conflict with Expo's build system.

Rule 3: EAS Build and Submit

The rule: 'Use EAS Build for all builds: eas build --platform ios, eas build --platform android. Never build locally with Xcode/Gradle unless debugging native issues. Configure builds in eas.json: development (dev client), preview (internal testing), production (store submission). Use EAS Submit for App Store/Play Store submission: eas submit --platform ios.'

For development builds: 'Use development builds (expo-dev-client) for development — not Expo Go (limited API access). Build once with eas build --profile development --platform ios. Install on your device. Use npx expo start --dev-client for the dev server. Development builds support all native modules — Expo Go only supports the Expo SDK subset.'

For OTA updates: 'Use EAS Update for over-the-air JavaScript updates: eas update --branch production. OTA updates skip the App Store review process — deploy bug fixes in minutes, not days. Configure update channels in eas.json. Only JavaScript changes can be OTA — native code changes require a new build.'

  • eas build for all builds — never local Xcode/Gradle for production
  • eas.json profiles: development, preview, production
  • Development builds with expo-dev-client — not Expo Go for real development
  • EAS Submit for store submission — eas submit --platform ios/android
  • EAS Update for OTA JS updates — skip App Store review for bug fixes

Rule 4: Config Plugins for Native Configuration

The rule: 'Use config plugins to modify native project files — never edit ios/ or android/ directly in a managed Expo project. Config plugins run at build time: they modify Info.plist, AndroidManifest.xml, Gradle files, and Podfile programmatically. Many Expo packages include built-in config plugins. For custom native config, write a config plugin in app.plugin.js.'

For app.json/app.config.ts: 'Configure everything in app.json or app.config.ts: app name, version, splash screen, icons, permissions, deep linking schemes, background modes. Use app.config.ts for dynamic config: export default ({ config }) => ({ ...config, extra: { apiUrl: process.env.API_URL } }). Access runtime config with expo-constants: Constants.expoConfig.extra.apiUrl.'

For permissions: 'Declare permissions in app.json: ios.infoPlist, android.permissions. Use expo-permissions for runtime permission requests. Never modify Info.plist or AndroidManifest.xml directly — use config plugins or app.json declarations.'

ℹ️ Never Edit ios/ or android/

In managed Expo, native directories are generated at build time. Config plugins modify them programmatically. Direct edits are overwritten on every build. Use config plugins in app.plugin.js for custom native configuration.

Rule 5: Expo-Specific Patterns

The rule: 'Use expo-constants for app metadata: version, bundleIdentifier, expoConfig. Use expo-updates for checking update availability. Use expo-splash-screen to control splash screen visibility: SplashScreen.preventAutoHideAsync() on startup, SplashScreen.hideAsync() when ready. Use expo-font for custom font loading: useFonts() hook.'

For assets: 'Use the asset system: require("./assets/image.png") for static assets. Use expo-asset for dynamic asset loading. Use expo-image for optimized image rendering (replaces Image from react-native). Configure assetBundlePatterns in app.json for offline asset bundling.'

For testing: 'Use Expo's testing setup: npx expo install jest-expo for Jest configuration. Use @testing-library/react-native for component tests. Use Maestro for end-to-end testing (Expo recommends it). Test on development builds — not Expo Go — for accurate behavior matching production.'

  • expo-constants for metadata — expo-updates for OTA check — expo-splash-screen for splash
  • expo-font + useFonts for custom fonts — expo-image for optimized images
  • require() for static assets — expo-asset for dynamic — assetBundlePatterns for offline
  • jest-expo for testing setup — @testing-library/react-native for components
  • Maestro for E2E — test on dev builds, not Expo Go

Complete Expo Rules Template

Consolidated rules for Expo SDK 51+ projects.

  • Expo Router: file-based navigation in app/ — _layout.tsx — Link and router.push
  • Expo SDK modules first — check compatibility before community packages — npx expo install
  • EAS Build for all builds — eas.json profiles — dev builds with expo-dev-client
  • EAS Update for OTA JS updates — EAS Submit for store submission
  • Config plugins for native config — never edit ios/ or android/ directly
  • app.config.ts for dynamic config — Constants.expoConfig for runtime access
  • expo-image over Image — expo-splash-screen — expo-font with useFonts
  • jest-expo + Testing Library — Maestro for E2E — test on dev builds
CLAUDE.md for Expo Projects — RuleSync Blog