ARCHITECTUREMay 12, 2026 // 13 min read // Written by Founders

DEMYSTIFYING EXPO PREBUILD: WHY DITCH EXPO GO IN PRODUCTION

Expo Go is a fantastic tool for prototyping. It lets you write React Native code and view it instantly on a physical device without compiling native code. But once your app goes to production, you need native modules (payment gateways, Bluetooth, custom analytics, push notifications). That is where expo prebuild comes in. Let's demystify the prebuild workflow and learn how to configure it in CI.

What is Expo Prebuild?

expo prebuild is a command that generates the dynamic /ios and /android native directories based on your app.json configuration. Instead of manually maintaining these folders, writing Swift/Java, or linking dependencies, you use Config Plugins to modify them programmatically.

This approach gives you the best of both worlds:

  1. The DX of Expo: Your project config is kept in a clean app.json file.
  2. Native Control: You can compile custom native modules and add proprietary frameworks without ejecting.

Why Ditch Expo Go for Production?

  1. Native Module Integration: If you need a native package (like Stripe, Bluetooth, or custom security SDKs), Expo Go will crash because the native code is not bundled. Prebuild generates custom native clients.
  2. Bundle Size Control: Expo Go includes every possible native library. An empty Expo Go app starts at ~50MB. Custom prebuilds let you trim unused resources.
  3. Pipeline Automation: You can generate and compile the native directories on the fly in CI, avoiding merge conflicts in native code.

Writing a Custom Config Plugin

Instead of manually editing AppDelegate.mm or MainActivity.java (which will be overwritten every time you prebuild), you write a Config Plugin in JavaScript or TypeScript. Here is an example of a plugin that modifies the Android manifest to allow custom hardware features:

import { withAndroidManifest, ConfigPlugin } from "@expo/config-plugins";

const withCustomHardware: ConfigPlugin = (config) => {
  return withAndroidManifest(config, async (config) => {
    const androidManifest = config.modResults;
    const mainApplication = androidManifest.manifest.application[0];
    
    // Add custom camera feature flag
    androidManifest.manifest["uses-feature"] = androidManifest.manifest["uses-feature"] || [];
    androidManifest.manifest["uses-feature"].push({
      $: {
        "android:name": "android.hardware.camera.autofocus",
        "android:required": "false"
      }
    });
    
    return config;
  });
};

export default withCustomHardware;

You then register this plugin inside your app.json:

{
  "expo": {
    "name": "MyProductionApp",
    "slug": "my-production-app",
    "plugins": ["./plugins/withCustomHardware"]
  }
}

Automating Prebuild on M4 Runners

By integrating expo prebuild into your M4 Apple Silicon pipeline, you get sub-minute React Native builds. Our prebuilt caching mechanisms store the node_modules and prebuild results, reducing compile times dramatically.

Here is the exact bash build step for your CI configuration:

# Clean install dependencies
npm ci

# Run prebuild to generate native iOS/Android directories
npx expo prebuild --platform all --no-install

# Compile the app to release binary using Fastlane
cd ios && pod install && cd ..
npx react-native build-android --mode=release

By generating these directories dynamically, you don't have to check the /ios and /android folder into Git. This prevents security leaks and avoids merge conflicts. Learn more about shrinking your final bundles in Shrinking React Native IPAs & APKs.

References & Citations

← BACK TO ARTICLES