TechSetupGuides
IntermediateExpoReact NativeMobile DevelopmentTypeScriptJavaScriptiOSAndroidCross-PlatformHermesMetroEAS Build

Expo: React Native Development Framework

Complete setup and integration guide for Expo - the official React Native framework recommended by the React Native team. Covers installation, managed vs bare workflows, native modules, EAS Build, over-the-air updates, the New Architecture, and production deployment patterns for building iOS and Android apps with TypeScript.

  1. Step 1

    What is Expo?

    Expo is an open-source framework built on top of React Native that simplifies the process of building, testing, and deploying mobile apps. With over 46,000 stars on GitHub, Expo is the official framework recommended by the React Native team for all new projects starting in 2026. Expo handles builds, deployments, and native APIs so you can ship iOS and Android apps from TypeScript without touching Xcode or Android Studio. The framework provides 50+ pre-built native modules including camera, location, file system, biometrics, push notifications, and more. As of 2026, with Expo SDK 55 and React Native 0.83, the New Architecture (featuring JSI, Fabric renderer, and TurboModules) is now mandatory, delivering near-native performance with improved startup times and 60fps animations.

  2. Step 2

    Core Architecture and Concepts

    Expo's 2026 architecture is built on the React Native New Architecture, which fundamentally changes how JavaScript communicates with native code:

    JSI (JavaScript Interface) - Makes native calls synchronous with direct access to native objects from JavaScript. No more asynchronous bridge overhead for layout measurements, native API calls, or file system access.

    Fabric Renderer - Integrates with React 18's concurrent model for smoother UI updates and better performance.

    TurboModules - Enables lazy-loading of native modules, reducing startup time and memory usage.

    Hermes Engine - JavaScript engine optimized for React Native. React Native 0.84 introduces Hermes V1 as the default, cutting startup time in half compared to JavaScriptCore.

    Metro Bundler - The official bundler maintained by Meta that serves as the central build tool, with first-class support for debugging and hot module replacement.

    Expo Router - File-based routing for mobile apps using the same mental model as Next.js. Routes are files, layouts are wrappers, and navigation is automatic.

    EAS (Expo Application Services) - Cloud infrastructure for builds, submissions, and over-the-air updates without requiring local Xcode or Android Studio installations.

    Expo Architecture (2026):
    ├── React Native New Architecture
    │   ├── JSI (synchronous native calls)
    │   ├── Fabric (concurrent renderer)
    │   └── TurboModules (lazy loading)
    ├── Hermes V1 (optimized JS engine)
    ├── Metro (bundler + HMR)
    ├── Expo Router (file-based routing)
    ├── Expo SDK 50+ modules
    └── EAS (builds, updates, submissions)
  3. Step 3

    System Requirements

    Before installing Expo, ensure your development environment meets these requirements. Expo supports macOS, Windows, and Linux for development, though macOS is required for building iOS apps locally with Xcode (note that EAS Build can build iOS apps in the cloud without a Mac).

    # Required
    Node.js 18+ (recommended: 22.14.0 or later)
    npm or yarn package manager
    
    # Check your Node.js version
    node --version
    # Should show v18.0.0 or higher
    
    # Optional (for local native builds only)
    Xcode 14+ (macOS only, for iOS development)
    Android Studio (for Android development)
    
    # Strongly recommended
    Expo Go app (iOS/Android) for testing on physical devices
    VS Code with React Native Tools extension
  4. Step 4

    Installation and Project Setup

    Expo CLI is now included with the expo package - no global installation needed. The create-expo-app command scaffolds a new project with sensible defaults and the latest SDK version. The process is streamlined and works on all platforms.

    # Create a new Expo project
    npx create-expo-app my-app
    
    # Navigate into the project
    cd my-app
    
    # Start the development server
    npx expo start
    
    # This will show a QR code and menu with options:
    # › Press a │ open Android
    # › Press i │ open iOS simulator
    # › Press w │ open web
    # › Press r │ reload app
    # › Press j │ open debugger
    # › Press m │ toggle menu
    ⚠ Heads up: The Expo Go app (available on iOS App Store and Google Play Store) is great for quick testing, but has limitations. For production features like custom native code or certain libraries, you'll need to create a development build using EAS Build.
  5. Step 5

    Testing on Physical Devices

    Expo Go is the fastest way to preview your app on a real device during development. Simply scan the QR code displayed when you run npx expo start. This works over your local network without cables or complex setup.

    # 1. Install Expo Go from app stores:
    # iOS: https://apps.apple.com/app/expo-go/id982107779
    # Android: https://play.google.com/store/apps/details?id=host.exp.exponent
    
    # 2. Start your dev server
    npx expo start
    
    # 3. Scan the QR code:
    # - iOS: Use the Camera app (built-in QR scanner)
    # - Android: Use the Expo Go app's built-in scanner
    
    # Your app will load on the device and hot-reload as you edit
  6. Step 6

    Project Structure Overview

    Expo projects using Expo Router follow a file-based routing convention. The app directory contains your routes, and the structure mirrors your app's navigation hierarchy.

    my-app/
    ├── app/                    # Expo Router routes (file-based)
    │   ├── _layout.tsx        # Root layout wrapper
    │   ├── index.tsx          # Home screen (/)
    │   ├── about.tsx          # About screen (/about)
    │   └── (tabs)/            # Tab navigation group
    │       ├── _layout.tsx
    │       ├── home.tsx
    │       └── profile.tsx
    ├── components/            # Reusable React components
    ├── constants/             # Colors, config, etc.
    ├── hooks/                 # Custom React hooks
    ├── assets/                # Images, fonts, etc.
    ├── app.json              # Expo configuration
    ├── package.json
    └── tsconfig.json
  7. Step 7

    Understanding Managed vs Bare Workflow

    Expo offers two workflow approaches, each with different tradeoffs between simplicity and control:

    Managed Workflow (Recommended for Most Apps) All native code is hidden and managed by Expo. You write only TypeScript/JavaScript, and building is handled through EAS Build. You get 50+ native APIs out of the box, over-the-air updates without app store approval, and no need for Xcode or Android Studio. This is the default when you run create-expo-app.

    Bare Workflow (When You Need Custom Native Code) Gives you complete control over the native projects. You can still use most Expo SDK modules and EAS Build, but you'll need to work directly with Xcode and Android Studio. Choose this when you need complex native integrations, custom SDKs, or platform-specific optimizations that aren't available through Expo modules.

    In 2026, the gap has narrowed significantly - most apps can stay in the managed workflow thanks to config plugins and the Expo Modules API. If a native feature isn't available, you can often write a custom module or use a config plugin without ejecting to bare workflow.

    # Start with managed workflow (default)
    npx create-expo-app my-app
    
    # If you later need native code access, you can migrate:
    # This generates ios/ and android/ directories
    npx expo prebuild
    
    # After prebuild, you can:
    # - Edit native code directly
    # - Add custom native modules
    # - Still use Expo SDK and EAS Build
    ⚠ Heads up: Once you run expo prebuild and commit the native directories, you're responsible for managing them. Config plugins let you customize native code without manually editing it, which is often a better approach.
  8. Step 8

    First Expo App with Expo Router

    Expo Router brings Next.js-style file-based routing to React Native. If you've built a Next.js app, the mental model is identical. Here's a simple app with two screens and navigation.

    // app/_layout.tsx - Root layout
    import { Stack } from 'expo-router';
    
    export default function RootLayout() {
      return <Stack />;
    }
    
    // app/index.tsx - Home screen
    import { View, Text, Button } from 'react-native';
    import { Link } from 'expo-router';
    
    export default function Home() {
      return (
        <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
          <Text style={{ fontSize: 24, marginBottom: 20 }}>Welcome to Expo!</Text>
          <Link href="/about" asChild>
            <Button title="Go to About" />
          </Link>
        </View>
      );
    }
    
    // app/about.tsx - About screen
    import { View, Text } from 'react-native';
    import { useRouter } from 'expo-router';
    
    export default function About() {
      const router = useRouter();
      
      return (
        <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
          <Text style={{ fontSize: 24 }}>About Screen</Text>
          <Button title="Go Back" onPress={() => router.back()} />
        </View>
      );
    }
  9. Step 9

    Key Expo SDK Modules

    Expo SDK provides over 50 pre-built native modules that work out of the box in the managed workflow. Here are the most commonly used ones. These modules are maintained by the Expo team and tested across platforms.

    // Camera - Take photos and videos
    import { CameraView, useCameraPermissions } from 'expo-camera';
    
    // Location - Get device location
    import * as Location from 'expo-location';
    const location = await Location.getCurrentPositionAsync({});
    
    // File System - Read/write files
    import * as FileSystem from 'expo-file-system';
    const content = await FileSystem.readAsStringAsync(fileUri);
    
    // Notifications - Local and push notifications
    import * as Notifications from 'expo-notifications';
    const token = await Notifications.getExpoPushTokenAsync();
    
    // Image Picker - Select from gallery
    import * as ImagePicker from 'expo-image-picker';
    const result = await ImagePicker.launchImageLibraryAsync();
    
    // SQLite - Local database
    import * as SQLite from 'expo-sqlite';
    const db = SQLite.openDatabaseSync('mydb.db');
    
    // Secure Store - Encrypted key-value storage
    import * as SecureStore from 'expo-secure-store';
    await SecureStore.setItemAsync('key', 'value');
    
    // Haptics - Vibration feedback
    import * as Haptics from 'expo-haptics';
    await Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium);
    
    // Biometric Authentication
    import * as LocalAuthentication from 'expo-local-authentication';
    const result = await LocalAuthentication.authenticateAsync();
  10. Step 10

    Setting Up Push Notifications (2026 Update)

    Expo provides a unified API for push notifications across iOS and Android. As of SDK 53, push notifications no longer work in Expo Go on Android - you must use a development build. Expo can handle the push notification service, or you can send directly from FCM/APNs.

    // app/_layout.tsx or app/index.tsx
    import * as Notifications from 'expo-notifications';
    import { useEffect, useRef, useState } from 'react';
    import { Platform } from 'react-native';
    
    // Configure how notifications are displayed
    Notifications.setNotificationHandler({
      handleNotification: async () => ({
        shouldShowAlert: true,
        shouldPlaySound: true,
        shouldSetBadge: false,
      }),
    });
    
    export default function App() {
      const [expoPushToken, setExpoPushToken] = useState('');
      const notificationListener = useRef<Notifications.Subscription>();
    
      useEffect(() => {
        // Get push token
        registerForPushNotificationsAsync().then(token => {
          setExpoPushToken(token ?? '');
        });
    
        // Listen for notifications
        notificationListener.current = Notifications.addNotificationReceivedListener(
          notification => {
            console.log('Notification received:', notification);
          }
        );
    
        return () => {
          notificationListener.current &&
            Notifications.removeNotificationSubscription(notificationListener.current);
        };
      }, []);
    
      return (
        // Your app here
      );
    }
    
    async function registerForPushNotificationsAsync() {
      if (Platform.OS === 'android') {
        await Notifications.setNotificationChannelAsync('default', {
          name: 'default',
          importance: Notifications.AndroidImportance.MAX,
        });
      }
    
      const { status: existingStatus } = await Notifications.getPermissionsAsync();
      let finalStatus = existingStatus;
      
      if (existingStatus !== 'granted') {
        const { status } = await Notifications.requestPermissionsAsync();
        finalStatus = status;
      }
      
      if (finalStatus !== 'granted') {
        return null;
      }
    
      const token = await Notifications.getExpoPushTokenAsync({
        projectId: 'your-project-id', // From app.json
      });
    
      return token.data;
    }
    ⚠ Heads up: Push notifications require a development build (not Expo Go) on Android as of SDK 53. Run npx eas build --profile development --platform android to create one, or use the iOS simulator which still supports notifications in Expo Go.
  11. Step 11

    Working with Native Device APIs

    Many device APIs require user permissions. Expo provides a consistent pattern for requesting permissions across all modules. Always request permissions before using sensitive APIs.

    import { useState } from 'react';
    import { Button, View, Image, Text } from 'react-native';
    import * as ImagePicker from 'expo-image-picker';
    import * as Location from 'expo-location';
    
    export default function PermissionsExample() {
      const [image, setImage] = useState<string | null>(null);
      const [location, setLocation] = useState<string>('');
    
      const pickImage = async () => {
        // Request permission
        const { status } = await ImagePicker.requestMediaLibraryPermissionsAsync();
        
        if (status !== 'granted') {
          alert('Permission to access gallery is required!');
          return;
        }
    
        // Launch picker
        const result = await ImagePicker.launchImageLibraryAsync({
          mediaTypes: ImagePicker.MediaTypeOptions.Images,
          allowsEditing: true,
          aspect: [4, 3],
          quality: 1,
        });
    
        if (!result.canceled) {
          setImage(result.assets[0].uri);
        }
      };
    
      const getLocation = async () => {
        const { status } = await Location.requestForegroundPermissionsAsync();
        
        if (status !== 'granted') {
          alert('Permission to access location is required!');
          return;
        }
    
        const loc = await Location.getCurrentPositionAsync({});
        setLocation(`${loc.coords.latitude}, ${loc.coords.longitude}`);
      };
    
      return (
        <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
          <Button title="Pick an image" onPress={pickImage} />
          {image && <Image source={{ uri: image }} style={{ width: 200, height: 200 }} />}
          
          <Button title="Get Location" onPress={getLocation} />
          {location && <Text>Location: {location}</Text>}
        </View>
      );
    }
  12. Step 12

    Configuration with app.json

    The app.json (or app.config.js for dynamic config) file controls your app's metadata, build settings, permissions, and more. This is where you configure everything from the app name to which device permissions you need.

    {
      "expo": {
        "name": "My Awesome App",
        "slug": "my-awesome-app",
        "version": "1.0.0",
        "orientation": "portrait",
        "icon": "./assets/icon.png",
        "splash": {
          "image": "./assets/splash.png",
          "resizeMode": "contain",
          "backgroundColor": "#ffffff"
        },
        "ios": {
          "supportsTablet": true,
          "bundleIdentifier": "com.yourcompany.myapp",
          "infoPlist": {
            "NSCameraUsageDescription": "This app uses the camera to take photos.",
            "NSLocationWhenInUseUsageDescription": "This app uses location to show nearby places."
          }
        },
        "android": {
          "package": "com.yourcompany.myapp",
          "permissions": [
            "CAMERA",
            "ACCESS_FINE_LOCATION",
            "READ_EXTERNAL_STORAGE",
            "WRITE_EXTERNAL_STORAGE"
          ],
          "adaptiveIcon": {
            "foregroundImage": "./assets/adaptive-icon.png",
            "backgroundColor": "#ffffff"
          }
        },
        "plugins": [
          "expo-router",
          [
            "expo-camera",
            {
              "cameraPermission": "Allow $(PRODUCT_NAME) to access your camera"
            }
          ]
        ],
        "extra": {
          "eas": {
            "projectId": "your-project-id"
          }
        }
      }
    }
    ⚠ Heads up: iOS permission descriptions (like NSCameraUsageDescription) are required for app store approval. Android permissions are automatically added by Expo modules, but you can declare them explicitly for clarity.
  13. Step 13

    Debugging with React Native DevTools

    Expo integrates with React Native DevTools (formerly Chrome DevTools) for debugging. Since Hermes is the default engine, debugging happens directly on the device rather than in a browser tab, providing more accurate performance profiling.

    # Start your app
    npx expo start
    
    # Press 'j' to open the debugger
    # This launches React Native DevTools in Chrome or Edge
    
    # Available debugging features:
    # - Console logs (console.log, warn, error)
    # - Network requests inspection
    # - React component tree
    # - Performance profiling
    # - Source maps for TypeScript
    
    # Alternative: VS Code debugging
    # 1. Install "React Native Tools" extension
    # 2. Add launch configuration:
    # {
    #   "type": "react-native",
    #   "request": "launch",
    #   "name": "Debug Expo",
    #   "cwd": "${workspaceFolder}"
    # }
    # 3. Press F5 to start debugging with breakpoints
  14. Step 14

    EAS Build: Cloud Builds Without Xcode

    EAS Build is Expo's cloud build service that compiles your app for iOS and Android without requiring local Xcode or Android Studio installations. This is especially valuable for Windows/Linux developers building iOS apps, or for CI/CD pipelines. The free plan includes 30 builds per month.

    # Install EAS CLI globally (one-time setup)
    npm install -g eas-cli
    
    # Login to your Expo account
    eas login
    
    # Configure EAS for your project
    eas build:configure
    # This creates eas.json with build profiles
    
    # Build for development (includes dev tools, runs on any device)
    eas build --profile development --platform ios
    eas build --profile development --platform android
    
    # Build for preview (production-like, for testing)
    eas build --profile preview --platform all
    
    # Build for production (app store submission)
    eas build --profile production --platform all
    
    # Check build status
    eas build:list
    
    # Download a completed build
    eas build:download --platform ios --latest
  15. Step 15

    EAS Build Configuration

    The eas.json file defines build profiles for different scenarios: development builds include debugging tools, preview builds are for testing the production experience, and production builds are for app store submission.

    {
      "build": {
        "development": {
          "developmentClient": true,
          "distribution": "internal",
          "ios": {
            "simulator": true
          }
        },
        "preview": {
          "distribution": "internal",
          "env": {
            "API_URL": "https://staging-api.example.com"
          }
        },
        "production": {
          "env": {
            "API_URL": "https://api.example.com"
          },
          "autoIncrement": true
        }
      },
      "submit": {
        "production": {
          "ios": {
            "appleId": "your-apple-id@example.com",
            "ascAppId": "1234567890"
          },
          "android": {
            "serviceAccountKeyPath": "./service-account-key.json",
            "track": "production"
          }
        }
      }
    }
  16. Step 16

    Over-the-Air (OTA) Updates with EAS Update

    One of Expo's most powerful features is the ability to push JavaScript and asset updates directly to users without going through app store review. This is perfect for bug fixes, content updates, and A/B testing. Note that native code changes still require a full app store submission.

    # Install the EAS Update library
    npx expo install expo-updates
    
    # Configure EAS Update in app.json
    # Add to your expo config:
    # "updates": {
    #   "url": "https://u.expo.dev/[your-project-id]"
    # }
    
    # Publish an update
    eas update --branch production --message "Fix login bug"
    
    # Create preview channels for testing
    eas update --branch staging --message "Test new feature"
    
    # View update history
    eas update:list
    
    # Roll back to a previous update
    eas update:republish --group <update-group-id>
    
    # In your app, you can check for updates programmatically:
    # import * as Updates from 'expo-updates';
    # const update = await Updates.checkForUpdateAsync();
    # if (update.isAvailable) {
    #   await Updates.fetchUpdateAsync();
    #   await Updates.reloadAsync();
    # }
    ⚠ Heads up: OTA updates can only change JavaScript code and assets. Native code changes (new libraries with native modules, permission changes, app.json native config) require a new build and app store submission.
  17. Step 17

    Environment Variables and Secrets

    Expo supports environment variables for configuration that changes between development, staging, and production. Never commit secrets to version control - use EAS Secrets for sensitive values.

    # Create .env file (add to .gitignore)
    API_URL=https://api.example.com
    API_KEY=your-dev-api-key
    
    # Install environment variable support
    npx expo install expo-constants
    
    # Access in your app with process.env (requires extra config)
    # Or use app.config.js for dynamic configuration:
    # export default {
    #   expo: {
    #     extra: {
    #       apiUrl: process.env.API_URL,
    #     },
    #   },
    # };
    
    # Access via Constants:
    # import Constants from 'expo-constants';
    # const apiUrl = Constants.expoConfig?.extra?.apiUrl;
    
    # For EAS Build, add secrets (not committed to repo):
    eas secret:create --scope project --name API_KEY --value your-prod-api-key
    
    # List secrets
    eas secret:list
    
    # Reference in eas.json:
    # "production": {
    #   "env": {
    #     "API_KEY": "$API_KEY"
    #   }
    # }
  18. Step 18

    App Store Submission with EAS Submit

    EAS Submit automates the process of uploading your app to the Apple App Store and Google Play Store. This eliminates the need for manual uploads through Xcode or the Play Console.

    # iOS Submission Requirements:
    # 1. Apple Developer account ($99/year)
    # 2. App created in App Store Connect
    # 3. Production build completed
    
    # Submit to App Store
    eas submit --platform ios --latest
    # Or specify a build ID:
    eas submit --platform ios --id <build-id>
    
    # Android Submission Requirements:
    # 1. Google Play Developer account ($25 one-time)
    # 2. App created in Play Console
    # 3. Service account key JSON (for API access)
    # 4. Production build completed
    
    # Submit to Google Play
    eas submit --platform android --latest
    
    # Submit to internal testing track first
    eas submit --platform android --track internal
    
    # Configure submission in eas.json (see EAS Build Configuration step)
    # Then just run:
    eas submit --platform all
    ⚠ Heads up: First-time App Store submissions must include privacy policy, app icons, screenshots, and descriptions. Allow 24-48 hours for Apple review. Google Play review is typically faster (hours to 1 day).
  19. Step 19

    Adding Third-Party Libraries

    Most React Native libraries work with Expo. For libraries with native code, check if they include a config plugin (the modern way) or if you need to run expo prebuild (which moves you toward bare workflow). The Expo documentation has a compatibility checker.

    # Install a library with native code
    npx expo install react-native-maps
    
    # If it has a config plugin, add to app.json:
    # "plugins": [
    #   "react-native-maps"
    # ]
    
    # Expo automatically applies the plugin during builds
    # No need to edit native code manually!
    
    # For libraries without config plugins:
    # 1. Check if someone created a community plugin
    # 2. Write a custom config plugin
    # 3. Or use expo prebuild for manual native access
    
    # Check library compatibility:
    # Visit: https://reactnative.directory/
    # Filter by "Expo Go" to see what works in managed workflow
    
    # Some popular libraries with config plugins:
    # - @react-native-firebase/app (via @react-native-firebase/expo-config)
    # - react-native-reanimated (via Expo SDK)
    # - react-native-gesture-handler (via Expo SDK)
    # - react-native-maps (config plugin included)
    # - expo-camera, expo-notifications, etc. (all Expo modules)
  20. Step 20

    Performance Optimization

    React Native and Expo provide several tools and techniques for optimizing app performance. The New Architecture (now mandatory in 2026) already provides significant improvements, but you should still follow best practices.

    // 1. Use React.memo for expensive components
    import { memo } from 'react';
    
    const ExpensiveComponent = memo(({ data }) => {
      return <View>{/* Complex rendering */}</View>;
    });
    
    // 2. Use useMemo and useCallback appropriately
    import { useMemo, useCallback } from 'react';
    
    function MyComponent({ items }) {
      const processedItems = useMemo(
        () => items.map(item => ({ ...item, processed: true })),
        [items]
      );
    
      const handlePress = useCallback(() => {
        console.log('Pressed');
      }, []);
    
      return <View>{/* Use processedItems */}</View>;
    }
    
    // 3. Optimize FlatList rendering
    import { FlatList } from 'react-native';
    
    <FlatList
      data={items}
      renderItem={({ item }) => <ItemComponent item={item} />}
      keyExtractor={(item) => item.id}
      // Performance props:
      removeClippedSubviews={true}
      maxToRenderPerBatch={10}
      updateCellsBatchingPeriod={50}
      initialNumToRender={10}
      windowSize={21}
      getItemLayout={(data, index) => ({
        length: ITEM_HEIGHT,
        offset: ITEM_HEIGHT * index,
        index,
      })}
    />
    
    // 4. Use Hermes (enabled by default)
    // Hermes is now the default engine - no action needed!
    
    // 5. Profile with React DevTools
    // Press 'j' in Expo dev server, use Profiler tab
  21. Step 21

    Testing Your Expo App

    Expo works with popular testing frameworks. Jest is included by default for unit tests, and you can add Detox or Maestro for end-to-end testing.

    # Jest is already configured in new Expo projects
    # Add test script to package.json if not present:
    # "scripts": {
    #   "test": "jest"
    # }
    
    # Run tests
    npm test
    
    # Example test file: __tests__/App.test.tsx
    import renderer from 'react-test-renderer';
    import App from '../App';
    
    it('renders correctly', () => {
      const tree = renderer.create(<App />).toJSON();
      expect(tree).toBeTruthy();
    });
    
    # For E2E testing, install Maestro (recommended)
    # https://maestro.mobile.dev/
    curl -Ls "https://get.maestro.mobile.dev" | bash
    
    # Create a flow file: .maestro/app.yaml
    # appId: com.yourcompany.myapp
    # ---
    # - launchApp
    # - tapOn: "Login"
    # - inputText: "user@example.com"
    # - tapOn: "Submit"
    # - assertVisible: "Welcome"
    
    # Run E2E tests
    maestro test .maestro/app.yaml
    
    # For React Native Testing Library:
    npm install --save-dev @testing-library/react-native
    
    # Example test:
    import { render, fireEvent } from '@testing-library/react-native';
    import { MyButton } from '../MyButton';
    
    test('button press', () => {
      const onPress = jest.fn();
      const { getByText } = render(<MyButton onPress={onPress} />);
      fireEvent.press(getByText('Press me'));
      expect(onPress).toHaveBeenCalled();
    });
  22. Step 22

    Production Deployment Checklist

    Before submitting to app stores, ensure you've completed these essential steps. Missing any of these can result in rejection or poor user experience.

    Pre-Launch Checklist:
    
    □ App Icons & Splash Screen
      - Icon: 1024x1024 PNG (iOS and Android)
      - Adaptive icon for Android
      - Splash screen image
    
    □ App Store Assets
      - Screenshots for all device sizes
      - App description and keywords
      - Privacy policy URL (required by Apple)
      - Support URL
    
    □ Configuration
      - Unique bundle identifier (iOS) / package name (Android)
      - Correct version number and build number
      - All required permissions with descriptions
      - Remove console.log statements
      - Disable dev warnings
    
    □ Testing
      - Test on physical iOS and Android devices
      - Test production build, not just development
      - Verify deep linking works
      - Test push notifications
      - Check offline behavior
      - Verify analytics are tracking
    
    □ Performance
      - App starts in < 3 seconds
      - No memory leaks
      - Images optimized
      - Minimal bundle size
    
    □ Compliance
      - Privacy policy covers data collection
      - GDPR compliance (if applicable)
      - COPPA compliance (if targeting children)
      - Terms of service
    
    □ Post-Launch
      - Set up EAS Update for quick fixes
      - Configure error tracking (Sentry, etc.)
      - Set up analytics (Expo Analytics, Firebase, etc.)
      - Monitor crash reports
  23. Step 23

    Common Debugging Tips and Solutions

    Here are solutions to common issues you might encounter when developing with Expo:

    Issue: "Metro bundler stuck or slow"
    Solution: npx expo start --clear
    
    Issue: "Can't connect to dev server on device"
    Solution: 
    - Ensure phone and computer on same Wi-Fi
    - Try tunnel mode: npx expo start --tunnel
    - Check firewall isn't blocking Metro (port 8081)
    
    Issue: "Module not found error after installing library"
    Solution:
    1. Stop dev server
    2. npx expo install <package>
    3. Clear cache: npx expo start --clear
    4. For native modules, rebuild: eas build --profile development
    
    Issue: "Push notifications not working"
    Solution:
    - SDK 53+: Must use development build on Android (not Expo Go)
    - Ensure you have projectId in app.json
    - Check permissions are granted
    - Verify push token is being sent to your backend
    
    Issue: "App crashes on launch (production build)"
    Solution:
    - Check logs: eas build:list → view build logs
    - Verify app.json config is correct
    - Ensure all assets are referenced correctly
    - Check for missing permissions
    - Use EAS Update to push a fix
    
    Issue: "OTA update not appearing"
    Solution:
    - Updates only check on app launch (cold start)
    - Runtime channel must match update branch
    - Check: eas update:list
    - Force check: Updates.checkForUpdateAsync()
    
    Issue: "Build failed on EAS"
    Solution:
    - Read the full build logs
    - Common causes: missing credentials, invalid config, incompatible dependencies
    - Try local build first: npx expo run:ios or npx expo run:android
    
    Debug Commands:
    - npx expo-doctor (diagnose common issues)
    - npx expo config --type public (view resolved config)
    - npx react-native info (environment info)
  24. Step 24

    When to Use Expo vs Bare React Native

    Choosing between Expo and bare React Native depends on your specific requirements. Here's a decision framework based on 2026 capabilities:

    Choose Expo When:

    • Building MVPs or prototypes rapidly
    • Team has limited native iOS/Android experience
    • Need fast iteration and OTA updates
    • Standard functionality (camera, notifications, location, etc.)
    • Small to medium-sized teams
    • Want to avoid Xcode and Android Studio
    • Need cloud builds (EAS Build)

    Choose Bare React Native When:

    • Require complex custom native code not available in Expo SDK
    • Need specific native SDKs without config plugins
    • Working with legacy native code that must integrate with React Native
    • Require platform-specific optimizations beyond what Expo provides
    • Have experienced native developers on team

    The Gap Has Narrowed Significantly: In 2026, Expo supports production-scale applications with custom native modules via the Expo Modules API, config plugins for native configuration, and access to the full New Architecture. Approximately 83% of SDK 54 projects use Expo with the New Architecture. Many apps that previously required bare React Native can now stay in the Expo managed workflow.

    Expo Advantages (2026):
    ✓ No Xcode/Android Studio required
    ✓ 50+ pre-built native modules
    ✓ EAS Build (cloud builds)
    ✓ OTA updates
    ✓ Faster development cycle
    ✓ File-based routing (Expo Router)
    ✓ Automatic app store submission
    ✓ Free tier available
    ✓ New Architecture fully supported
    
    Expo Limitations:
    ✗ App size ~2-4 MB larger (includes Expo SDK)
    ✗ Some obscure native libraries lack config plugins
    ✗ Must use development builds for some features (not Expo Go)
    ✗ OTA updates can't change native code
    
    Bare React Native Advantages:
    ✓ Full control over native code
    ✓ Can use any native library
    ✓ Slightly smaller app size
    ✓ Direct access to latest React Native features
    
    Bare React Native Tradeoffs:
    ✗ Requires Xcode and Android Studio
    ✗ Manual native configuration
    ✗ Longer build times
    ✗ Native expertise required
    ✗ No built-in OTA updates
    ✗ Manual app store submissions
  25. Step 25

    Advanced: Custom Native Modules with Expo Modules API

    If you need a native feature that isn't in the Expo SDK and no library exists, you can write a custom native module using the Expo Modules API. This is easier than writing traditional React Native native modules and works in both managed and bare workflows.

    # Create a local Expo module
    npx create-expo-module my-native-module
    
    # This creates a module structure:
    # my-native-module/
    # ├── android/        # Android native code (Kotlin)
    # ├── ios/            # iOS native code (Swift)
    # ├── src/            # TypeScript API
    # └── expo-module.config.json
    
    # Example module: MyNativeModule/src/index.ts
    import { requireNativeModule } from 'expo-modules-core';
    
    const MyNativeModule = requireNativeModule('MyNativeModule');
    
    export function getDeviceName(): string {
      return MyNativeModule.getDeviceName();
    }
    
    # iOS implementation (Swift): MyNativeModule/ios/MyNativeModule.swift
    import ExpoModulesCore
    
    public class MyNativeModule: Module {
      public func definition() -> ModuleDefinition {
        Name("MyNativeModule")
        
        Function("getDeviceName") {
          return UIDevice.current.name
        }
      }
    }
    
    # Android implementation (Kotlin): MyNativeModule/android/src/main/java/expo/modules/mynativemodule/MyNativeModule.kt
    package expo.modules.mynativemodule
    
    import expo.modules.kotlin.modules.Module
    import expo.modules.kotlin.modules.ModuleDefinition
    
    class MyNativeModule : Module() {
      override fun definition() = ModuleDefinition {
        Name("MyNativeModule")
        
        Function("getDeviceName") {
          return android.os.Build.MODEL
        }
      }
    }
    
    # Install in your app:
    npx expo install ./my-native-module
    
    # Use in your app:
    import { getDeviceName } from 'my-native-module';
    const deviceName = getDeviceName();
    ⚠ Heads up: Custom native modules require running expo prebuild and creating development builds. They can't be used with Expo Go. Consider searching npm and GitHub first - someone may have already built what you need.
  26. Step 26

    Monitoring and Analytics

    Production apps need monitoring for crashes, errors, and user analytics. Expo integrates with popular services, and some features are built into EAS.

    # Option 1: Sentry (Error tracking)
    npm install @sentry/react-native
    npx expo install expo-dev-client
    
    # Configure in app.json:
    # "plugins": [
    #   [
    #     "@sentry/react-native/expo",
    #     {
    #       "organization": "your-org",
    #       "project": "your-project"
    #     }
    #   ]
    # ]
    
    # Initialize in app/_layout.tsx:
    import * as Sentry from '@sentry/react-native';
    
    Sentry.init({
      dsn: 'your-sentry-dsn',
      enableInExpoDevelopment: false,
      debug: false,
    });
    
    # Option 2: Firebase Analytics
    npx expo install expo-firebase-analytics
    # Follow Firebase setup guide
    
    # Option 3: Expo Analytics (built into EAS)
    # Automatically enabled for EAS projects
    # View in Expo dashboard: expo.dev/analytics
    
    # Track custom events:
    import * as Analytics from 'expo-firebase-analytics';
    
    Analytics.logEvent('purchase', {
      item_id: 'SKU_123',
      price: 9.99,
      currency: 'USD',
    });
    
    # Performance monitoring:
    import * as Sentry from '@sentry/react-native';
    
    const transaction = Sentry.startTransaction({
      name: 'load-user-profile',
    });
    // ... do work ...
    transaction.finish();
  27. Step 27

    Resources and Next Steps

    Official Documentation:

    • Expo Docs: https://docs.expo.dev/
    • React Native Docs: https://reactnative.dev/
    • Expo Router: https://docs.expo.dev/router/introduction/

    GitHub Repositories:

    • Expo: https://github.com/expo/expo (46K+ stars)
    • React Native: https://github.com/facebook/react-native (120K+ stars)

    Essential Tools:

    • Expo Dashboard: https://expo.dev/ (manage projects, builds, updates)
    • React Native Directory: https://reactnative.directory/ (library compatibility)
    • EAS CLI: Global tool for builds and submissions

    Community:

    • Expo Discord: https://chat.expo.dev/
    • Expo Forums: https://forums.expo.dev/
    • React Native Community Discord: https://www.reactiflux.com/
    • Stack Overflow: Use tags [expo] and [react-native]

    Learning Resources:

    • Expo YouTube Channel: Official tutorials and talks
    • React Native Express: https://www.reactnative.express/ (free interactive guide)
    • Expo Examples: https://github.com/expo/examples (sample apps)
    • Official Blog: https://blog.expo.dev/

    Getting Help:

    • For bugs: File issues on GitHub (expo/expo)
    • For questions: Expo Forums or Discord
    • For paid support: Expo offers enterprise plans with SLA

    Next Steps:

    1. Complete the Expo tutorial: https://docs.expo.dev/tutorial/introduction/
    2. Build a simple app using Expo Router and Expo SDK modules
    3. Set up EAS Build and create development builds
    4. Experiment with OTA updates
    5. Deploy a production app to both app stores
    6. Join the Expo community on Discord for ongoing support

Feature requests

Sign in to suggest features or vote on existing ones.

No feature requests yet.

Discussion

0 people marked this as worked·Sign in to mark your own.

Sign in to join the discussion.

No comments yet.