Stack Overview

Expo is best understood as three layers:

flowchart TD
    product["Product code"] --> expo["Expo app framework"]
    expo --> nativeBinary["Android and iOS binaries"]
    expo --> eas["Optional EAS services"]
    product --> js["JavaScript bundle and assets"]
    product --> appConfig["App config"]
    appConfig --> nativeBinary
    js --> nativeBinary
    eas --> nativeBinary

What Runs Where

PieceLives inRelease implication
React components, hooks, storesJavaScript bundleCan usually ship by app build or compatible OTA update
Images, fonts, copyAssets and bundleCan usually ship by app build or compatible OTA update
Expo Router route treeJavaScript bundle plus deep-link configRoute code can be OTA if runtime-compatible; native link config may need rebuild
App identifiers, icons, splash, permissionsNative binary generated from app configRequires rebuild and store release
Native libraries and modulesNative binaryRequires development build and release build
EAS Build/Submit/Update configEAS project plus eas.json and app configAffects build, store upload, and OTA targeting

The Practical Mental Model

An Expo app is not just JavaScript. It is a native Android/iOS app containing:

  • Native runtime and dependencies.
  • JavaScript bundle.
  • Assets.
  • App identifiers, permissions, icons, splash, and platform config.
  • Optional update client that can fetch compatible JavaScript/assets later.

That model prevents the two common mistakes: treating native changes as OTA-safe and treating Expo Go as proof of release behavior.