optimizing bundle and assets systematically
The app size of a React Native app determines install rates, store warnings, and reach in markets with slow connections. Android App Bundle with R8 shrinking, iOS App Thinning, WebP images, and continuous size monitoring in the CI pipeline shrink app size measurably, without sacrificing functionality.
Table of Contents
- 1. Why app size is a business metric
- 2. Where the bytes actually go
- 3. Android: App Bundle, R8, and ABI splits
- 4. iOS: App Thinning and architecture slices
- 5. Analyzing JS bundle size
- 6. Optimizing images and assets
- 7. Auditing native dependencies for bloat
- 8. Continuous size monitoring in CI
- 9. App size reduction techniques compared
- 10. Summary
- 11. FAQ
1. Why app size is a business metric
A large app size is not merely a technical detail, it is a measurable figure with direct impact on install rates. On slow or metered connections, users abandon the download before installation even begins, especially when the Play Store or App Store already shows a size warning before a user even taps "Install". In growth markets with limited mobile data plans, every extra megabyte costs real, measurable installs.
The Play Store shows explicit download size warnings past certain thresholds, and the App Store restricts downloading larger apps over cellular without a Wi-Fi confirmation. Both mechanisms directly affect the conversion rate between a store page visit and an actual install, an effect that shows up clearly in app analytics.
Before working on app size, it helps to set a concrete target: a typical production React Native app bundle should stay well under 50 MB on Android and under 100 MB on iOS, depending on feature scope and target audience. That target is the baseline against which every optimization step below gets measured.
2. Where the bytes actually go
Before reducing app size, you need to know where the megabytes actually go. A typical React Native app size budget splits across the JavaScript bundle, native binaries or architecture slices, images and fonts, and native third-party SDKs. Without this breakdown, optimization turns into guesswork, spending time in the wrong place.
The Android APK Analyzer, built directly into Android Studio, visualizes the size distribution of an APK or AAB by file type and directory, immediately showing whether native libraries, resources, or the JS bundle account for the largest share. On iOS, Xcode provides an app size report (App Thinning Size Report) that breaks down size per device variant and shows how much each architecture slice contributes to the total package.
These analysis steps should sit at the start of every optimization round, not after. Measuring first tells you whether the effort of configuring ProGuard, converting to WebP, or removing a native SDK is even worth it before spending development time.
3. Android: App Bundle, R8, and ABI splits
The single most effective lever for reducing app size on Android is switching from a classic APK to an Android App Bundle (.aab). Play Feature Delivery generates an optimized, device-specific APK for each device from it, containing only the resources and CPU architectures actually needed, instead of shipping a universal APK with everything for everyone.
Additionally, R8, the successor to ProGuard, reduces size through code shrinking (removing unused code), resource shrinking, and obfuscation. Together, these three steps can noticeably cut the final app footprint, especially in projects with many unused library functions or resources coming from third-party SDKs.
// android/app/build.gradle
android {
buildTypes {
release {
// Enable R8 code shrinking and obfuscation
minifyEnabled true
// Remove unused resources not referenced by shrunk code
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
// Split output by CPU architecture instead of shipping all ABIs
splits {
abi {
enable true
reset()
include 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
universalApk false
}
}
bundle {
// Let Play Feature Delivery generate per-device optimized APKs
language { enableSplit = true }
density { enableSplit = true }
abi { enableSplit = true }
}
}
A commonly overlooked side effect of ABI splits: without this configuration, the same APK containing all four CPU architectures is shipped for every install, even though a device only needs a single one. This app size reduction is therefore a pure configuration win, without changing a single line of app code.
4. iOS: App Thinning and architecture slices
On iOS, App Thinning plays a similar role to AAB splits on Android: the App Store delivers only the matching assets and architecture slices for each device, instead of distributing a universal package with every image variant and CPU architecture. On-Demand Resources complements this by loading assets not needed immediately at launch only when actually required, instead of bundling them permanently into the initial download.
A common but easily fixable problem in React Native apps with many third-party frameworks: these frameworks often also contain simulator slices or architectures not needed at all for the final store build. Removing these unused slices from embedded frameworks before archiving noticeably reduces app size, without affecting any functionality on real devices.
Asset catalogs (`.xcassets`) with correctly configured compression are the second lever: images that accidentally end up uncompressed or at too high a resolution in the catalog needlessly inflate app size. Xcode compresses asset catalogs automatically, but only if the source images themselves are not already inefficiently large raw data.
5. Analyzing JS bundle size
Compared to modern web bundlers, React Native's Metro bundler has only limited tree-shaking, meaning accidentally imported dead code often ends up in the final bundle instead of being automatically removed. This gap makes targeted bundle analysis an important step in reducing app size.
Tools like `react-native-bundle-visualizer` or `source-map-explorer` visualize which modules take up how much space in the JS bundle, usually as an interactive treemap. A classic finding: a full import of `lodash` instead of a single function, or an entire icon font set, even though the app only actually uses a handful of icons.
# Generate a source map and analyze bundle composition
npx react-native bundle \
--platform android \
--dev false \
--entry-file index.js \
--bundle-output android-release.bundle \
--sourcemap-output android-release.bundle.map
npx source-map-explorer android-release.bundle android-release.bundle.map
The most common finding from this analysis is not a single giant module, but many small, accidentally fully imported libraries whose sum contributes significantly to app size. Named imports instead of default imports of entire libraries usually solve this problem with no functional loss.
6. Optimizing images and assets
Images are the single largest contributor to app size in most React Native apps, often bigger than the entire JS bundle. Converting PNG to WebP often reduces file size substantially with barely perceptible quality loss, without needing to adjust layout or rendering code.
A second common mistake: instead of correct `@2x`/`@3x` asset variants, a single oversized image gets used for all screen densities. The operating system then scales this image down at runtime, while the app carries around unnecessarily large raw data for every display density. Correctly sized assets per density level are usually the fastest way to save noticeable space.
# Convert PNG assets to WebP with high quality, low size overhead
for f in assets/images/*.png; do
cwebp -q 85 "$f" -o "${f%.png}.webp"
done
# Compare original vs converted size
du -sh assets/images/*.png assets/images/*.webp
SVG is often the most space-efficient alternative to raster images for simple icons and illustrations, since a single vector format covers all resolutions instead of maintaining several raster versions. For photos and complex images, WebP remains the better choice. Remote images not needed in every case should generally be lazy-loaded rather than baked permanently into the app bundle.
7. Auditing native dependencies for bloat
Native SDKs for analytics, ads, or payment processing often add several megabytes per library, independent of the app's actual JS code. These costs show up directly in app size and are easily overlooked when looking only at the JS bundle, because they occur exclusively at the native level.
After every major feature release, it is worth taking stock of the autolinking configuration: native modules no longer referenced by removed JS code often stay linked in the build configuration anyway and keep getting compiled in. An audit of actually used native packages against `package.json` reliably uncovers such leftovers.
Overlapping libraries are another common pattern: two different native HTTP client modules, an old one and a newly introduced one existing side by side in the project, double native dependency costs with no additional benefit. Consolidating onto a single library per functional area often reduces app size surprisingly noticeably.
8. Continuous size monitoring in CI
Without continuous monitoring, app size creeps up gradually with every feature, every new dependency, and every added image, often with no single commit that can be blamed for it. A size budget in the CI pipeline makes this gradual increase visible before it accumulates across many releases.
A simple approach: after every build, measure the resulting AAB or IPA size and compare it against a defined threshold. If the build exceeds that value, the pipeline fails or flags the pull request for manual review, similar to a performance regression test.
#!/usr/bin/env bash
# ci-check-app-size.sh: fail the build if AAB size regresses
set -euo pipefail
MAX_SIZE_MB=45
AAB_PATH="android/app/build/outputs/bundle/release/app-release.aab"
size_bytes=$(stat -c%s "$AAB_PATH")
size_mb=$((size_bytes / 1024 / 1024))
echo "Current AAB size: ${size_mb} MB (budget: ${MAX_SIZE_MB} MB)"
if (( size_mb > MAX_SIZE_MB )); then
echo "ERROR: App size exceeds budget of ${MAX_SIZE_MB} MB" >&2
exit 1
fi
Fastlane lanes can be extended with such size checks and additionally provide a historical record documenting how app size evolved release by release. This discipline treats size regressions with the same care as performance regressions, instead of only noticing them once users or store reviews already react.
9. App size reduction techniques compared
Not every technique delivers the same return relative to effort. The table below places the most important options for reducing app size side by side.
| Technique | Typical savings | Implementation effort | Risk |
|---|---|---|---|
| R8/ProGuard shrinking | High | Low, one-time config | Low, secure with testing |
| Android App Bundle / ABI splits | High | Low | Very low |
| WebP conversion | Medium | Low, batch script | Low, visual check needed |
| Removing unused dependencies | Variable, sometimes high | Medium, audit needed | Medium, functionality risk |
R8 shrinking and AAB splits deliver the best ratio of savings to effort, since they are almost purely configuration changes. Removing unused dependencies requires more care, since an incomplete audit can break functionality, but it often shrinks app size the most when done correctly.
Mironsoft
App size audits and build optimization for React Native
Want to measurably shrink app size without risking functionality?
We analyze your AAB, IPA and JS bundle, identify the biggest savings potential, and set up R8 shrinking, AAB splits, and CI size monitoring, so your app size stays within target permanently.
Size audit
APK Analyzer and App Thinning report analysis with concrete savings potential
Build configuration
Setting up R8 shrinking, AAB splits, and App Thinning correctly and safely
CI size budget
Automated size checks per build, before regressions go live
10. Summary
The app size of a React Native app is most effectively reduced with a combination of build configuration and asset discipline: Android App Bundle with ABI splits and R8 shrinking on Android, App Thinning and cleaned up framework slices on iOS, WebP conversion for images, and targeted JS bundle analysis against accidentally fully imported libraries.
The most sustainable lever, however, is continuous monitoring rather than a one-time optimization pass: a size budget in the CI pipeline prevents gradual growth from accumulating unnoticed across many releases. Treating app size as a recurring metric instead of a one-off project is what keeps it within target permanently.
Reducing React Native App Size: Key Takeaways
Android App Bundle
AAB with ABI splits delivers device-specific, smaller APKs instead of a universal build.
R8 shrinking
`minifyEnabled true` and `shrinkResources true` automatically strip unused code and resources.
Images & assets
WebP conversion and correct density variants often save the single largest share.
CI size budget
Automated size checks per build prevent gradual, unnoticed growth.