skip to content

Since React Native 0.79, why is the Android JavaScript bundle stored uncompressed in the APK, and what does enableBundleCompression trade off?

level: middleimportance: nice to knowfreq 22%

answer

  1. 0.79 changed the default
  2. no decompression before launch
  3. memory-mapped straight from the APK
  4. enableBundleCompression defaults to false
  5. bigger install size, not download

basics

~20 s

An uncompressed bundle can be memory-mapped straight from the APK instead of being decompressed before the app starts, which shortens Android startup. Setting enableBundleCompression to true shrinks the installed app but brings that startup cost back.

solid answer

~40 s

Before React Native 0.79 the JavaScript bundle asset was compressed inside the APK, so Android had to decompress it before the app could run it, a noticeable slice of cold start. Since 0.79 the React Native Gradle plugin marks the bundle's file extension as **not compressed** by default, so the bundle can be **memory-mapped** directly. The switch is `enableBundleCompression` in the `react { }` block of `android/app/build.gradle`, default `false`; in an Expo project using prebuild, `expo-build-properties` exposes it as `android.enableBundleCompression`. The trade-off is **storage on the device**: the installed app is larger. The download is largely unaffected because APKs are compressed in transit. Keep the default unless install size is a hard constraint for your users, and measure startup on a low-end Android device if you change it. The setting is Android-only.

code

json · 14 lines
json
{
  "expo": {
    "plugins": [
      [
        "expo-build-properties",
        {
          "android": {
            "enableBundleCompression": true
          }
        }
      ]
    ]
  }
}

go deeper

for a junior

Know that since React Native 0.79 the Android JavaScript bundle ships uncompressed by default to start faster.

for a middle

Explain why skipping decompression and memory-mapping the bundle speeds startup, where enableBundleCompression lives, and its expo-build-properties equivalent.

for a senior

Show you weigh install size against cold-start time with measurements on low-end Android release builds before changing the default.

for a principal

Discuss how storage limits in your user base and the startup targets you have set decide this setting, and who owns that decision.

## What changed in 0.79 An Android app ships as an APK (or is installed from an app bundle that produces APKs). Files inside it can be stored **compressed** or **uncompressed**. React Native's JavaScript bundle - the asset named `index.android.bundle` by default - used to be compressed like most assets. That had a startup cost: before React Native could run the bundle, the system had to **decompress** it. From **React Native 0.79**, the bundle is stored **uncompressed by default**. The 0.79 release notes describe this as a significant Android startup improvement delivered by a one-line default change. ## Why uncompressed is faster The React Native Gradle plugin documents the reasoning on the setting itself: disabling compression for the bundle allows it to be **directly memory-mapped** into RAM, improving startup time at the cost of a larger APK. In practice: - **Compressed**: read the compressed bytes, inflate them into memory, then hand them to the JavaScript engine. - **Uncompressed**: map the file's bytes from the APK and let the engine read them as needed. The work saved happens on every cold start, before any of your code runs, and weighs most on slower Android devices. ## The switch and its default The setting lives in the `react { }` block that the React Native Gradle plugin reads from `android/app/build.gradle`: ```groovy react { // Default is false: the bundle is stored uncompressed. // true = smaller installed app, slower startup. enableBundleCompression = true } ``` When it is `false`, the plugin adds the bundle's file extension to the Android build's **no-compress** list, which is how the asset ends up stored uncompressed. In an **Expo** project that generates its native folders with prebuild, you do not edit `build.gradle` directly. The `expo-build-properties` config plugin exposes the same switch as `android.enableBundleCompression`, also defaulting to `false` (see the code example). ## The trade-off | | Uncompressed (default since 0.79) | Compressed (`enableBundleCompression = true`) | |---|---|---| | Cold start | faster: no decompression step | slower: bundle must be inflated first | | Installed size on device | larger | smaller | | APK size | larger | smaller | | Download size | largely unchanged, since APKs are compressed in transit | about the same | The release notes stress the last row: the APK grows, but users mostly do not pay for it in download size because APKs are compressed when downloaded from the network. The real cost is **storage on the device**. ## Where this sits in a cold start It helps to place the setting on the startup timeline. A cold start roughly runs native process start and React Native initialisation, then loading the bundle, then evaluating the modules your entry file imports, then the first render and any data the first screen waits for. Bundle packaging affects only the **loading** step, and it is a fixed cost paid on every launch regardless of what your code does. That makes it an unusually good trade: one build setting, no code change, a saving on every cold start. It also means it cannot fix the later phases; an app that evaluates every screen at launch or waits on the network before showing its home screen stays slow with or without compression. ## When to change it Most apps should keep the default. Reasons to turn compression back on: - Your users are on devices where **install size** is a real constraint and the startup difference is small for your bundle. - You have measured both settings on your slowest supported devices and the startup cost of compression is acceptable. Whatever you choose, **measure**: build release variants with each setting, install on a mid-range or low-end Android phone, and compare cold-start time to the first interactive screen across several launches. ## Pitfalls - **Expecting an iOS effect.** The setting is part of the Android Gradle plugin; iOS is unaffected. - **Editing `build.gradle` in a prebuild-managed Expo project**, where the next prebuild regenerates the file; use `expo-build-properties` instead. - **Judging the change by download size.** The store download barely moves; look at installed size and startup time. - **Assuming the default was always uncompressed.** Apps upgraded from before 0.79 got the change automatically unless they had set the option; a custom template may still differ. - **Treating it as a substitute for startup work.** It removes one fixed cost; it does not reduce how much JavaScript runs before the first screen.

  • Why do users barely notice the larger APK when they install the app?
    APKs are compressed when they are downloaded from the network, so an uncompressed asset inside the APK still travels compressed. The extra cost shows up as storage on the device after installation rather than as a larger download.
  • In an Expo project with continuous native generation, where would you turn bundle compression back on, and why not in build.gradle?
    In `app.json` or `app.config.ts`, through the `expo-build-properties` config plugin's `android.enableBundleCompression` option. Prebuild regenerates the `android` folder, so a manual `build.gradle` edit would be overwritten.

saying these in an interview costs you the question

  • An uncompressed bundle makes the store download much larger
  • enableBundleCompression also speeds up iOS startup
  • Compressing the bundle makes startup faster because there is less to read
  • The bundle has been uncompressed in every React Native version
  • In an Expo prebuild project you should edit build.gradle directly