git clone https://github.com/NeverSight/learn-skills.dev
T=$(mktemp -d) && git clone --depth=1 https://github.com/NeverSight/learn-skills.dev "$T" && mkdir -p ~/.claude/skills && cp -r "$T/data/skills-md/agents-inc/skills/mobile-framework-expo" ~/.claude/skills/neversight-learn-skills-dev-mobile-framework-expo && rm -rf "$T"
data/skills-md/agents-inc/skills/mobile-framework-expo/SKILL.mdExpo Development Patterns
Quick Guide: Build production-ready React Native apps with Expo. Use managed workflow with Continuous Native Generation for most projects, Expo Router for file-based navigation, and EAS for builds/updates. Development builds replace Expo Go for production testing.
<critical_requirements>
CRITICAL: Before Using This Skill
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,
, named constants)import type
(You MUST use development builds for production testing - Expo Go is for prototyping only)
(You MUST update runtimeVersion when making native dependency changes to prevent OTA update crashes)
(You MUST use config plugins for native customization - NEVER manually edit android/ios directories in managed workflow)
(You MUST use
prefix for client-side environment variables - NEVER store secrets in these variables)EXPO_PUBLIC_
</critical_requirements>
Auto-detection: Expo, expo-router, EAS Build, EAS Update, expo-dev-client, app.config.js, app.json, expo prebuild, npx expo, eas.json, expo-constants, expo-notifications, Continuous Native Generation, CNG
When to use:
- Starting new React Native projects with rapid development needs
- Building apps that need OTA (over-the-air) updates
- Using file-based routing with convention-over-configuration
- Managing native code without maintaining android/ios directories
- Deploying to app stores with cloud builds
Key patterns covered:
- Managed workflow with Continuous Native Generation (CNG)
- Expo Router file-based navigation
- EAS Build, Submit, and Update workflows
- Development builds vs Expo Go
- Config plugins for native customization
- Environment configuration and secrets
- Push notifications setup
When NOT to use:
- Apps requiring complex custom native code beyond Expo Modules API
- When app size must be under 15MB (Expo adds overhead)
- Legacy React Native projects not ready for migration
<philosophy>
Philosophy
Expo transforms React Native development from "write once, debug everywhere" to "write once, deploy confidently." The key insight is that most apps don't need direct native access - they need well-maintained native modules with consistent APIs.
Core principles:
- Managed by default - Let Expo handle native complexity; prebuild only when necessary
- Continuous Native Generation - Treat android/ios as build artifacts, not source code
- Development builds for truth - Expo Go is for learning; development builds show production reality
- OTA for velocity - Ship JavaScript updates without app store delays
- Config plugins over ejection - Customize native code declaratively when needed
Mental model:
Expo is NOT a limitation on React Native - it's a professional-grade abstraction. You can always drop down to native code via Expo Modules API or prebuild, but most apps never need to.
</philosophy><patterns>
Core Patterns
Pattern 1: Dynamic Configuration with app.config.ts
app.config.tsUse
app.config.ts for environment-specific builds. Use named constants for SDK versions and build numbers.
// app.config.ts - Environment-aware config const IS_PRODUCTION = process.env.APP_ENV === "production"; const BUILD_NUMBER = 1; export default ({ config }: ConfigContext): ExpoConfig => ({ ...config, name: IS_PRODUCTION ? "MyApp" : "MyApp (Dev)", ios: { bundleIdentifier: IS_PRODUCTION ? "com.app" : "com.app.dev", buildNumber: String(BUILD_NUMBER), }, android: { package: IS_PRODUCTION ? "com.app" : "com.app.dev", versionCode: BUILD_NUMBER, }, });
Full examples: examples/core.md - App Configuration section
Pattern 2: Config Plugins for Native Customization
Modify native code declaratively -- changes survive
expo prebuild --clean. Use config plugins for permissions, SDK versions, and native settings.
// app.config.ts plugins array plugins: [ [ "expo-camera", { cameraPermission: "Allow $(PRODUCT_NAME) to access your camera." }, ], [ "expo-build-properties", { android: { minSdkVersion: 24 }, ios: { deploymentTarget: "15.1" } }, ], ];
Full examples: examples/core.md - Config Plugins section
Pattern 3: Environment Variables
Use
EXPO_PUBLIC_ prefix for client-side variables. Metro requires direct property access -- destructuring and bracket notation don't work.
// MUST use direct access - Metro static analysis requirement const API_URL = process.env.EXPO_PUBLIC_API_URL; // Works // const { EXPO_PUBLIC_API_URL } = process.env; // BROKEN - undefined at runtime
Full examples: examples/core.md - Environment Variables section
Pattern 4: Development Builds
Use
expo-dev-client for production-accurate testing. Expo Go is for prototyping only -- it lacks your native dependencies, push notifications, and accurate splash screens.
# Cloud build eas build --profile development --platform ios # Local build npx expo run:ios
Full configuration: examples/eas.md - Development Builds section
Pattern 5: Asset Management
Block splash screen while loading fonts, use
expo-image for remote images with blur hash placeholders and disk caching.
SplashScreen.preventAutoHideAsync(); // Load fonts, then call SplashScreen.hideAsync() when ready
</patterns>Full examples: examples/core.md - Font Loading and Image Handling sections
<red_flags>
RED FLAGS
- Expo Go for production testing -- missing native modules, push notifications, accurate splash screens. Always use development builds.
- Not updating runtimeVersion after native changes -- OTA updates crash on apps with incompatible native code. Use
policy for automatic detection."fingerprint" - Storing secrets in
variables -- embedded in JS bundle, visible to anyone who decompiles. Use EAS Secrets and backend proxies.EXPO_PUBLIC_ - Manually editing android/ios directories -- changes lost on
. Use config plugins.expo prebuild --clean - Destructuring
-- Metro requires direct property access (process.env
). Destructuring and bracket notation produceprocess.env.EXPO_PUBLIC_*
.undefined - Using
-- removed in SDK 55. Migrate toexpo-av
andexpo-video
.expo-audio - Legacy Architecture -- removed after SDK 54. React Native 0.82+ requires New Architecture.
Full anti-patterns and gotchas: reference.md
</red_flags>
Detailed Resources:
- examples/core.md - Project config, environment variables, fonts, images
- examples/router.md - File-based routing, tabs, auth flows, modals
- examples/eas.md - Cloud builds, app store submission, OTA updates
- reference.md - Decision frameworks, SDK compatibility, anti-patterns
<critical_reminders>
CRITICAL REMINDERS
All code must follow project conventions in CLAUDE.md
(You MUST use development builds for production testing - Expo Go is for prototyping only)
(You MUST update runtimeVersion when making native dependency changes to prevent OTA update crashes)
(You MUST use config plugins for native customization - NEVER manually edit android/ios directories in managed workflow)
(You MUST use
prefix for client-side environment variables - NEVER store secrets in these variables)EXPO_PUBLIC_
Failure to follow these rules will cause OTA update crashes, broken builds, and security vulnerabilities.
</critical_reminders>