Back to all posts
October 31, 2025Charlie BrownDevelopment

Expo Development Workflow and Best Practices

Master Expo development workflow with EAS Build, over-the-air updates, native modules, and production deployment strategies.

Expo Development Workflow and Best Practices

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

bash
npx create-expo-app MyApp --template
cd MyApp

2. Install EAS CLI

bash
npm install -g eas-cli
eas login
eas build:configure

3. Configure app.json

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

bash
# 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 --android

2. Using Expo Go

Expo Go allows instant testing without building:

typescript
// 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:

bash
# Create development build
eas build --profile development --platform ios
eas build --profile development --platform android

# Install on device
eas build:run -p ios

EAS Build Configuration

eas.json Setup

json
{
  "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

bash
# 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 all

Over-the-Air Updates (EAS Update)

1. Setup EAS Update

bash
eas update:configure

2. Create Update

typescript
// 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

bash
# Publish update
eas update --branch production --message "Bug fixes"

# Publish with specific runtime version
eas update --branch production --runtime-version 1.0.0

4. Update Strategy

typescript
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

typescript
// 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

json
{
  "expo": {
    "plugins": [
      "./plugins/withCustomConfig",
      [
        "expo-camera",
        {
          "cameraPermission": "Allow $(PRODUCT_NAME) to access your camera"
        }
      ]
    ]
  }
}

Environment Variables

1. Setup

bash
# Install dotenv
npm install dotenv

# Create .env files
.env.development
.env.production

2. Configuration

typescript
// 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

typescript
import Constants from 'expo-constants';

const apiUrl = Constants.expoConfig?.extra?.apiUrl;

Testing

1. Unit Tests

typescript
// __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

typescript
// 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

typescript
import { lazy, Suspense } from 'react';

const HeavyScreen = lazy(() => import('./screens/HeavyScreen'));

function App() {
  return (
    <Suspense fallback={<Loading />}>
      <HeavyScreen />
    </Suspense>
  );
}

2. Asset Optimization

bash
# Optimize images
npx expo-optimize

# Generate adaptive icons
npx expo prebuild

Deployment

1. iOS Deployment

bash
# Build for App Store
eas build --platform ios --profile production

# Submit to App Store
eas submit --platform ios --profile production

2. Android Deployment

bash
# Build for Play Store
eas build --platform android --profile production

# Submit to Play Store
eas submit --platform android --profile production

3. Internal Distribution

bash
# Build for internal testing
eas build --profile preview --platform all

# Share build URL
eas build:list

CI/CD Integration

GitHub Actions Example

yaml
# .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

json
{
  "expo": {
    "version": "1.0.0",
    "ios": {
      "buildNumber": "1"
    },
    "android": {
      "versionCode": 1
    },
    "runtimeVersion": "1.0.0"
  }
}

2. Error Tracking

typescript
import * as Sentry from '@sentry/react-native';

Sentry.init({
  dsn: 'your-sentry-dsn',
  enableInExpoDevelopment: true,
  debug: __DEV__,
});

3. Analytics

typescript
import * as Analytics from 'expo-firebase-analytics';

Analytics.logEvent('screen_view', {
  screen_name: 'Home',
  screen_class: 'HomeScreen',
});

Troubleshooting

Common Issues

  1. Build Failures: Check EAS build logs
  2. Update Not Working: Verify runtime version matches
  3. Native Module Issues: Ensure config plugins are correct
  4. 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.

References

Want more insights?

Subscribe to our newsletter or follow us for more updates on software development and team scaling.

Contact Us