Production-ready patterns for React Native development with Expo, including navigation, state management, native modules, and offline-first architecture.
- Starting a new React Native or Expo project
- Implementing complex navigation patterns
- Integrating native modules and platform APIs
- Building offline-first mobile applications
- Optimizing React Native performance
- Setting up CI/CD for mobile releases
src/
├── app/ # Expo Router screens
│ ├── (auth)/ # Auth group
│ ├── (tabs)/ # Tab navigation
│ └── _layout.tsx # Root layout
├── components/
│ ├── ui/ # Reusable UI components
│ └── features/ # Feature-specific components
├── hooks/ # Custom hooks
├── services/ # API and native services
├── stores/ # State management
├── utils/ # Utilities
└── types/ # TypeScript types
| Feature | Expo | Bare RN |
|---|
Setup complexity | Low | High |
Native modules | EAS Build | Manual linking |
OTA updates | Built-in | Manual setup |
Build service | EAS | Custom CI |
Custom native code | Config plugins | Direct access |
# Create new Expo project
npx create-expo-app@latest my-app -t expo-template-blank-typescript
# Install essential dependencies
npx expo install expo-router expo-status-bar react-native-safe-area-context
npx expo install @react-native-async-storage/async-storage
npx expo install expo-secure-store expo-haptics
Before running these commands, replace the latest tag with a specific reviewed version of the scaffolding package and of the template, and confirm the package name and publisher with the user. Expo install selects versions compatible with the current Expo SDK; commit the generated lockfile, install from that lockfile in CI, and review newly added dependencies before relying on them.
// app/_layout.tsx
import { Stack } from 'expo-router'
import { ThemeProvider } from '@/providers/ThemeProvider'
import { QueryProvider } from '@/providers/QueryProvider'
export default function RootLayout() {
return (
<QueryProvider>
<ThemeProvider>
<Stack screenOptions={{ headerShown: false }}>
<Stack.Screen name="(tabs)" />
<Stack.Screen name="(auth)" />
<Stack.Screen name="modal" options={{ presentation: 'modal' }} />
</Stack>
</ThemeProvider>
</QueryProvider>
)
}
Detailed pattern documentation lives in references/details.md. Read that file when the navigation tier above is insufficient.
- Use Expo - Faster development, OTA updates, managed native code
- FlashList over FlatList - Better performance for long lists
- Memoize components - Prevent unnecessary re-renders
- Use Reanimated - 60fps animations on native thread
- Test on real devices - Simulators miss real-world issues
- Keep sensitive data in expo-secure-store - Store tokens, credentials and personal data in expo-secure-store; AsyncStorage is unencrypted, so reserve it for non sensitive cache and preferences
- Sign OTA updates - Enable code signing for EAS Update, use channels and runtime versions to control which builds receive an update, test on a preview channel before production, and limit who can publish
- Protect release credentials - Keep EAS tokens, signing keystores and store API keys in the CI secret store or EAS credentials, out of version control and out of logs, and scope and rotate tokens
- Don't inline styles - Use StyleSheet.create for performance
- Don't fetch in render - Use useEffect or React Query
- Don't ignore platform differences - Test on both iOS and Android
- Don't store secrets in code or the app bundle - Environment variables and app config extra values bundled into the app are public; keep privileged secrets on your server behind your API, and keep per user tokens in expo-secure-store
- Don't skip error boundaries - Mobile crashes are unforgiving