How big can a React Native bundle get before it hurts?
There's no hard limit on JS bundle size — which is exactly the problem. Where the real costs kick in (startup, OTA delivery, your bandwidth bill) and how to find out what's actually in yours.
Developers ask this expecting a number — some ceiling where the bundle stops working. The honest answer is that no such ceiling exists: the store size limits people half-remember apply to the app binary, not to the JavaScript inside it, and neither Hermes nor any OTA mechanism enforces a maximum. Which is worse than a limit, really. A limit would fail loudly. Bundle bloat just quietly charges you in three currencies.
Currency one: startup time
Every byte of your bundle participates in startup. Hermes softens this dramatically — bytecode is compiled ahead of time and memory-mapped rather than parsed — but bigger bundles still mean more I/O and more code initialization, and module-level side effects run regardless of engine. React Native's Hermes documentation explains that its build pipeline compiles JavaScript to bytecode for faster startup, not a bundle-size exemption. Measure startup on representative devices and set a budget from your own baseline; there is no universal MB threshold.
Currency two: OTA delivery
The moment you ship updates over the air, bundle size becomes a per-release, per-device cost. A 15 MB bundle pushed to 100,000 devices is 1.5 TB of transfer for a one-line fix — real money under any bandwidth-metered pricing, as we worked through in what OTA updates actually cost, and real failure surface too: big downloads on flaky mobile networks abandon partway, which shows up as the fleet-adoption lag described in the update-debugging checklist. A provider that supports delta delivery can reduce repeat-download cost, but its behavior and fallback rules are provider-specific. A bloated bundle still costs you on fresh installs and whenever a client has to fetch a full update.
Currency three: the assets you forgot
When a bundle is surprisingly large, the JavaScript is usually not the culprit — the assets riding with it are. A hero image exported at 4x, an animation JSON nobody compressed, three icon fonts of which you use eleven glyphs. Audit JavaScript and assets together; the treemap will tell you where the worthwhile wins are.
Finding out what's actually in there
- Generate a source-map visualization (tools like
source-map-exploreror Expo Atlas work on RN bundles) and stare at the treemap. Everyone finds a surprise the first time — the entire locale data of a date library is the classic. - Check your heaviest dependencies for lighter idioms: per-function imports instead of whole-library imports, modern date libraries instead of the legacy giants, and no, you probably don't need that 2 MB utility belt for four functions.
- Defer rarely used screens with lazy
requireso their cost is paid on navigation, not at startup. - Put a bundle-size check in CI with a soft budget. Not to block merges — to make growth visible. Bloat is never one decision; it's forty invisible ones.
The rule of thumb
Treat bundle size like a budget you review, not a limit you fear. Know your number, know your treemap, watch the trend, and let the occasional cleanup sprint pay for itself in faster startups and a smaller bandwidth bill. And if your updates are megabytes when your changes are lines, fix the delivery mechanism before the diet — shipping only what changed beats shrinking what didn't.