Expo Development Workflow and Best Practices
Expo simplifies React Native development by providing a comprehensive toolchain for building, testing, and deploying mobile apps. This article explores advanced Expo workflows, EAS (Expo Application Services), and best practices for production apps.
Understanding Expo Architecture
Expo provides:
- Expo SDK: Pre-built native modules
- EAS Build: Cloud-based native builds
- EAS Update: Over-the-air updates
- Expo Go: Development client
- Config Plugins: Custom native configuration
Project Setup
1. Initialize Expo Project
npx create-expo-app MyApp --template
cd MyApp2. Install EAS CLI
npm install -g eas-cli
eas login
eas build:configure3. Configure app.json
{
"expo": {
"name": "MyApp",
"slug": "my-app",
"version": "1.0.0",
"orientation": "portrait",
"icon": "./assets/icon.png",
"userInterfaceStyle": "automatic",
"splash": {
"image": "./assets/splash.png",
"resizeMode": "contain",
"backgroundColor": "#ffffff"
},
"ios": {
"supportsTablet": true,
"bundleIdentifier": "com.yourcompany.myapp"
},
"android": {
"adaptiveIcon": {
"foregroundImage": "./assets/adaptive-icon.png",
"backgroundColor": "#ffffff"
},
"package": "com.yourcompany.myapp"
},
"plugins": ["expo-router"],
"extra": {
"eas": {
"projectId": "your-project-id"
}
}
}
}Development Workflow
1. Local Development
# Start development server
npx expo start
# Start with tunnel (for testing on physical devices)
npx expo start --tunnel
# Start with specific platform
npx expo start --ios
npx expo start --android2. Using Expo Go
Expo Go allows instant testing without building:
// App.tsx
import { StatusBar } from 'expo-status-bar';
import { StyleSheet, Text, View } from 'react-native';
export default function App() {
return (
<View style={styles.container}>
<Text>Open up App.tsx to start working on your app!</Text>
<StatusBar style="auto" />
</View>
);
}3. Development Build
For apps using custom native code:
# Create development build
eas build --profile development --platform ios
eas build --profile development --platform android
# Install on device
eas build:run -p iosEAS Build Configuration
eas.json Setup
{
"cli": {
"version": ">= 5.0.0"
},
"build": {
"development": {
"developmentClient": true,
"distribution": "internal",
"ios": {
"simulator": true
}
},
"preview": {
"distribution": "internal",
"ios": {
"simulator": false
}
},
"production": {
"autoIncrement": true,
"env": {
"API_URL": "https://api.production.com"
}
}
},
"submit": {
"production": {
"ios": {
"appleId": "your@email.com",
"ascAppId": "your-app-id"
},
"android": {
"serviceAccountKeyPath": "./service-account.json",
"track": "internal"
}
}
}
}Build Profiles
# Development build
eas build --profile development --platform all
# Preview build (for TestFlight/Internal Testing)
eas build --profile preview --platform ios
# Production build
eas build --profile production --platform allOver-the-Air Updates (EAS Update)
1. Setup EAS Update
eas update:configure2. Create Update
// app.json
{
"expo": {
"updates": {
"url": "https://u.expo.dev/your-project-id",
"enabled": true,
"checkAutomatically": "ON_LOAD",
"fallbackToCacheTimeout": 0
},
"runtimeVersion": "1.0.0"
}
}3. Publish Updates
# Publish update
eas update --branch production --message "Bug fixes"
# Publish with specific runtime version
eas update --branch production --runtime-version 1.0.04. Update Strategy
import * as Updates from 'expo-updates';
async function checkForUpdates() {
try {
const update = await Updates.checkForUpdateAsync();
if (update.isAvailable) {
await Updates.fetchUpdateAsync();
await Updates.reloadAsync();
}
} catch (error) {
console.error('Error checking for updates:', error);
}
}
// Check on app start
useEffect(() => {
checkForUpdates();
}, []);Custom Native Modules
1. Config Plugins
// plugins/withCustomConfig.ts
import { ConfigPlugin } from '@expo/config-plugins';
const withCustomConfig: ConfigPlugin = (config) => {
// Modify native configuration
return config;
};
export default withCustomConfig;2. Using Config Plugins
{
"expo": {
"plugins": [
"./plugins/withCustomConfig",
[
"expo-camera",
{
"cameraPermission": "Allow $(PRODUCT_NAME) to access your camera"
}
]
]
}
}Environment Variables
1. Setup
# Install dotenv
npm install dotenv
# Create .env files
.env.development
.env.production2. Configuration
// app.config.js
require('dotenv').config({ path: `.env.${process.env.NODE_ENV}` });
export default {
expo: {
extra: {
apiUrl: process.env.API_URL,
apiKey: process.env.API_KEY,
},
},
};3. Usage
import Constants from 'expo-constants';
const apiUrl = Constants.expoConfig?.extra?.apiUrl;Testing
1. Unit Tests
// __tests__/App.test.tsx
import { render, screen } from '@testing-library/react-native';
import App from '../App';
test('renders correctly', () => {
render(<App />);
expect(screen.getByText('Welcome')).toBeTruthy();
});2. E2E Tests with Detox
// e2e/App.e2e.ts
describe('App', () => {
beforeAll(async () => {
await device.launchApp();
});
it('should show welcome screen', async () => {
await expect(element(by.id('welcome'))).toBeVisible();
});
});Performance Optimization
1. Code Splitting
import { lazy, Suspense } from 'react';
const HeavyScreen = lazy(() => import('./screens/HeavyScreen'));
function App() {
return (
<Suspense fallback={<Loading />}>
<HeavyScreen />
</Suspense>
);
}2. Asset Optimization
# Optimize images
npx expo-optimize
# Generate adaptive icons
npx expo prebuildDeployment
1. iOS Deployment
# Build for App Store
eas build --platform ios --profile production
# Submit to App Store
eas submit --platform ios --profile production2. Android Deployment
# Build for Play Store
eas build --platform android --profile production
# Submit to Play Store
eas submit --platform android --profile production3. Internal Distribution
# Build for internal testing
eas build --profile preview --platform all
# Share build URL
eas build:listCI/CD Integration
GitHub Actions Example
# .github/workflows/build.yml
name: Build and Submit
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '18'
- run: npm install
- run: npm install -g eas-cli
- run: eas build --platform all --profile production --non-interactive
env:
EXPO_TOKEN: ${{ secrets.EXPO_TOKEN }}Best Practices
1. Version Management
{
"expo": {
"version": "1.0.0",
"ios": {
"buildNumber": "1"
},
"android": {
"versionCode": 1
},
"runtimeVersion": "1.0.0"
}
}2. Error Tracking
import * as Sentry from '@sentry/react-native';
Sentry.init({
dsn: 'your-sentry-dsn',
enableInExpoDevelopment: true,
debug: __DEV__,
});3. Analytics
import * as Analytics from 'expo-firebase-analytics';
Analytics.logEvent('screen_view', {
screen_name: 'Home',
screen_class: 'HomeScreen',
});Troubleshooting
Common Issues
- Build Failures: Check EAS build logs
- Update Not Working: Verify runtime version matches
- Native Module Issues: Ensure config plugins are correct
- Performance: Use Expo Dev Tools for profiling
Conclusion
Expo provides a powerful toolchain for React Native development. By mastering EAS Build, EAS Update, config plugins, and deployment workflows, you can efficiently build, test, and deploy production-ready mobile applications. Follow best practices for versioning, error tracking, and performance optimization to create robust apps.



