Before the first release of a project, Play Console showed my Expo app at 27.5 MB. That seemed large for a fairly simple app. If your Expo Android app size is larger than you expect, the cause is probably not your JavaScript. In my case the app's own code was 4.4 MB. Most of the rest was library bytecode that Expo doesn't shrink by default, and a few lines of config cut the download by about 37%.
Measuring what's in the Android APK
I didn't want to guess, so I pulled the installed APKs off the emulator and looked inside. This was the unshrunk build:
| What | Uncompressed size | Notes |
|---|---|---|
classes*.dex | about 47 MB | Compiled Java and Kotlin from React Native, Expo modules, and other Android libraries |
| Native libraries (arm64 split) | about 23 MB | Compiled C++ for React Native, the Hermes engine, and native modules |
index.android.bundle | 4.4 MB | My JavaScript, compiled to Hermes bytecode |
| Icon fonts and resources | about 2 MB | Fonts, images, and Android resources |
Why Expo apps are large: R8 is off by default
The 47 MB of dex is the surprise. Almost none of it is mine. It's every class from every library I depend on, whether my app calls it or not. R8 is Android's code shrinker, optimizer, and obfuscator. It walks the code reachable from your app's entry points and drops the rest. A new Expo project ships release builds with R8 turned off, so none of that happens.
The catch: R8 can remove a class a library only looks up by name at runtime, and the app crashes when someone reaches that code. The fix is a keep rule telling R8 to leave that class alone.
Turning on R8 with expo-build-properties
The expo-build-properties config plugin exposes the switches. Install it:
npx expo install expo-build-propertiesThen add it to the plugins array in app.json:
{
"expo": {
"plugins": [
[
"expo-build-properties",
{
"android": {
"enableMinifyInReleaseBuilds": true,
"enableShrinkResourcesInReleaseBuilds": true
}
}
]
]
}
}enableMinifyInReleaseBuilds turns on R8 for the code. enableShrinkResourcesInReleaseBuilds removes resources nothing references.
Config plugins run at build time, so start a new EAS build (or run npx expo prebuild --clean for local builds) to pick up the change.
Check the option name
In recent Expo SDKs (52 and later) the option is named enableMinifyInReleaseBuilds. I tested this on SDK 57. Older docs and blog posts call it enableProguardInReleaseBuilds, so if you copy a snippet and nothing changes, check the name.
Results: 27.5 MB down to 17.3 MB
I built again with R8 on and repeated the same measurements:
| Before (no shrinking) | After (R8) | |
|---|---|---|
| dex files | 47 MB | 16.7 MB |
base.apk, uncompressed | 53.5 MB | 23.1 MB |
| Download size | 27.5 MB | 17.3 MB |
That's 37% smaller!
Takeaway
Measure before guessing. In a React Native or Expo app it's mostly libraries, not your JavaScript, so trimming your own code or compressing images would have barely moved the number here.
Turn on R8 early and test every screen on a real release build. Given how R8 works, it's far easier to find a missing keep rule when the app is small and you know every screen than after months of features.