Mobile — App Performance Optimization
Hermes Engine Tuning for Startup Apps
Direct answer
Hermes improves React Native startup by compiling JavaScript to bytecode at build time, so the device skips parsing and compiling on every launch. To get its full benefit, verify Hermes is actually running in your release build, ship the bytecode uncompressed on Android so it can be memory-mapped, drop redundant Intl polyfills after checking coverage, and keep module-level code cheap so lazy loading pays off. For an early-stage app, these tweaks are usually the cheapest startup wins available.
Hermes is the default engine in modern React Native, but defaults only get you the baseline — I still find startup regressions in apps that technically have Hermes enabled. This is how I tune it on client projects where cold start time directly affects activation.
Key facts, with sources
- The median crash-free session rate across mobile apps in 2025 was 99.95%, with top-performing teams at the 75th percentile holding 99.99%. (Luciq Mobile App Stability Outlook 2025)
- Compared with JavaScriptCore, the Hermes engine cuts typical React Native app startup from about 4.5 seconds to about 2.0 seconds, memory from about 185 MB to 136 MB, and engine app-size overhead from about 12 MB to 8 MB. (OneUptime Blog)
- Real-world production migrations to React Native's New Architecture report around 43% faster cold starts, 39% faster rendering, and 26% lower memory usage. (RapidNative)
- Shopify's performance bar for its React Native apps is critical screens loading in under 500 milliseconds at the 75th percentile, alongside over 99.9% crash-free sessions. (Shopify Engineering)
- Published optimization case studies include Discord cutting app startup time in half and Coinbase reporting an 80% funnel performance improvement after its React Native rewrite. (CatDoes)
What Hermes actually changes at launch
A classic JS engine receives source code, parses it, compiles it, then executes. Hermes moves the first two steps to your build machine: Metro output is compiled ahead of time into Hermes bytecode, and the device loads that bytecode directly. On low-end Android hardware — where your growth cohort usually lives — skipping parse and compile is the difference between a sluggish launch and an acceptable one.
Hermes also uses a garbage collector designed for mobile constraints, which tends to keep memory flatter than desktop-derived engines. The practical consequence for a startup: you get meaningful startup and memory headroom without touching product code, provided the engine is genuinely active and configured to load the bundle efficiently.
Verify Hermes is really running
I have audited more than one app that believed it shipped Hermes but did not — a stale gradle flag, a podfile override left from a debugging session, or a CI pipeline building against an old configuration. The definitive check is at runtime: Hermes injects a HermesInternal global, so log its presence once at startup in an internal build and confirm it in the release variant, not just in dev.
Also confirm the artifact itself: the bundle inside a release Android build should be Hermes bytecode, not plain JavaScript. If you see readable JS in the APK's assets, the bytecode compilation step is being skipped and you are paying full parse cost on every cold start.
// Log once at app start in an internal release build
const isHermes = () => !!(global as any).HermesInternal;
console.log('Hermes enabled:', isHermes());Let Android memory-map the bytecode
By default, assets inside an APK can be stored compressed. A compressed Hermes bundle must be inflated into RAM before execution on every single launch — you pay CPU time and memory for the privilege. If the bytecode is stored uncompressed, Hermes can memory-map the file instead: pages load on demand, launch does less work, and the OS can evict pages under pressure.
The fix is one build config change: tell the Android build not to compress the bundle asset. Recent templates handle this for you, but I still check it explicitly on ejected, brownfield, and long-lived projects, because it silently disappears during upgrades more often than you would expect.
android {
// Store the Hermes bytecode uncompressed so it can be
// memory-mapped at startup instead of inflated into RAM
aaptOptions {
noCompress "bundle"
}
}Audit Intl and polyfill weight
Older Hermes versions lacked internationalization APIs, so teams pulled in JavaScript Intl polyfills — number formatting, date-time formatting, plural rules — that are individually large and collectively enormous. Modern Hermes ships Intl support, which makes many of those polyfills dead weight that still gets parsed, loaded, and initialized at startup.
Do not rip them out blindly. Check which specific Intl methods your app and its dependencies call, verify Hermes coverage for those methods on the versions of iOS and Android you support, then remove polyfills one at a time with a locale-heavy screen as your regression test. On apps that internationalized early, I have found this to be one of the larger single startup improvements available.
Write JS that cooperates with lazy loading
Hermes plus inline requires means modules can be loaded when first used rather than at boot — but only if your code lets them. Module-level side effects defeat this: a file that builds a large configuration object, initializes an SDK, or runs a loop at import time forces that cost to startup no matter how lazily it is required.
In tuning passes I look for three patterns: heavy work at module scope that should move inside a function or be wrapped in lazy initialization, singletons that eagerly construct at import, and index files whose imports chain-load half the app. Fixing these is unglamorous refactoring, but combined with bytecode and mmap it is what makes cold start feel genuinely fast on cheap devices.
When to hire senior help
Bring in a senior performance specialist when your crash-free rate sits below roughly 99.9%, startup exceeds a couple of seconds on mid-range Android, or the team lacks profiling experience with Hermes, the JS thread, and native tooling. Performance rescue work is diagnostic in nature, so a short expert engagement typically finds the handful of hot spots that in-house teams spend months guessing at. If your stack includes React Native + Python + AI, a senior engineer who owns the full product beats coordinating multiple juniors.
Bottom line
Dhairya Senjaliya ships Mobile — App Performance Optimization projects worldwide — book a scoping call to discuss your specific situation.
Common pitfalls to avoid
- ✕Profiling only on high-end iPhones in dev mode, then discovering the real user base on low-end Android devices experiences multi-second startups and dropped frames
- ✕Rendering long feeds with ScrollView or an untuned FlatList instead of a virtualized list like FlashList, blocking the JS thread during scroll
- ✕Passing new inline functions and object literals on every render without memoization, causing cascading re-renders that never show up until lists grow
- ✕Ignoring JS bundle size and startup path, shipping no lazy loading or inline requires, so time-to-interactive balloons as the app grows
Frequently asked questions
Is Hermes enabled by default in React Native?
Yes — Hermes has been the default JavaScript engine for React Native for several versions, on both iOS and Android. However, defaults can be overridden by old gradle flags, Podfile settings, or CI configuration, especially in apps that have lived through many upgrades. Always verify at runtime by checking for the HermesInternal global in a release build rather than trusting configuration files.
How much does Hermes improve React Native startup time?
It depends on bundle size and device class, so distrust universal numbers. The improvement comes from skipping JavaScript parse and compile at launch, which is most dramatic on low-end Android hardware and large bundles. Measure your own cold start with performance markers before and after enabling or tuning Hermes — that measured delta on a mid-range device is the only number that matters for your app.
Do I still need Intl polyfills with Hermes?
Usually not anymore. Modern Hermes versions include Intl implementations covering common formatting APIs, making most JavaScript polyfills redundant startup weight. But coverage varies by Hermes version and method, so list the Intl APIs your app actually calls, confirm support on your minimum OS targets, and remove polyfills incrementally while testing locale-sensitive screens rather than deleting them all at once.
Which performance metrics should we actually track?
Crash-free session rate (the 2025 median is 99.95%, and below 99.8% is a red flag), cold start time, time-to-interactive, P75 screen-load time, and frame rate during scrolling. These need real-user monitoring in production, not just lab measurements.
Why is our React Native app slow on Android but fine on iOS?
The usual causes are testing only on flagship devices, JS-thread-blocking work, unvirtualized lists, and running an old React Native architecture or the JSC engine. Hermes and New Architecture migrations show measured gains of roughly 26 to 43% on memory and cold start, so upgrading the foundation is often the highest-leverage fix.
Does performance really affect revenue or is it an engineering vanity metric?
It compounds directly into acquisition and retention: crashes and slowness drive uninstalls and one-star reviews, and lower ratings measurably cut store conversion rates. That is why top consumer apps hold themselves to 99.99% crash-free sessions and sub-500ms screen loads.
Bottom line: Dhairya Senjaliya ships Mobile — App Performance Optimization projects worldwide. Book a scoping call at https://dhairyasenjaliya.com/#book-call.