I have been shipping mobile apps as an indie developer since 2014, and the apps under my account have accumulated more than 50 million downloads. At that scale, the balance between startup time and memory is directly tied to AdMob revenue. An app that feels heavy in the first second loses the user before the first ad ever appears, and that gap shows up in the dashboard the next morning.
Over the past two months I have been running three apps rebuilt from Rork templates with Hermes enabled in production. There are plenty of tutorial-style write-ups about switching the engine, but it is surprisingly hard to find honest two-month-later notes. So I am leaving mine here.
The Setup and the Devices I Measured
The three apps are all on Expo SDK 53 with jsEngine: "hermes" in app.json, which is the default. As a baseline I kept the Firebase Performance Monitoring and Crashlytics dashboards from the JSC (JavaScriptCore) era, so the before-and-after numbers come from the same instrumentation, not a fresh harness.
I measured on four devices that sit on my desk: iPhone 13, iPhone 15 Pro, Pixel 7, and the second-generation iPhone SE. The reason I keep an older device in the loop is that more than forty percent of installs across my 50 million downloads still come from phones that are at least three years old. Judging an indie app only on a flagship is a quick way to misread the field.
The collection script is a small in-house logger that combines the app launch timestamp from expo-application with react-native-performance marks. I took ten sessions per device per day for twenty-eight days and used the median, which smooths out the occasional cold-system spike.
Cold Start: A Thirty-Eight Percent Cut, with Caveats
In the JSC era the average cold start was 2.4 seconds on the SE and 0.9 seconds on the iPhone 15 Pro. After two weeks of bedding-in on Hermes, those numbers moved to 1.6 seconds and 0.55 seconds. That is a thirty-three percent improvement on the SE, thirty-nine percent on the 15 Pro, and a thirty-eight percent average.
The headline number feels good, but the breakdown is more sober. The bytecode precompilation that Hermes is famous for only covers the region from "native bridge ready" to "first render." In my apps that region accounts for about sixty percent of total startup. The remaining forty percent is the AdMob SDK initializing, Firebase booting, and RevenueCat restoring purchases. None of that shrinks because of Hermes.
So the realistic expectation is "a roughly fifty percent cut in five-to-six-tenths of the budget." You need to pair Hermes with lazy initialization for ad and billing SDKs to feel a difference users would actually notice. As someone whose monthly AdMob revenue depends on this, every second shaved off cold start lifts my first-ad reach rate by four to six percentage points.
Memory: An Eighteen Percent Drop on the Pixel 7
The memory side surprised me on Android. The resident-set size on the Pixel 7 after returning from the home screen dropped from a JSC-era mean of 142 MB to 116 MB — roughly eighteen percent. iPhone improvements were smaller, in the eight to ten percent range.
My read is that the Hermes heap model meshes well with the Android Runtime's generational GC. The concrete payoff in my catalog was that a long-standing crash class — "background restart due to memory pressure" on the Pixel 7 — fell from twenty-three issues over two months to just three. A backlog that I had been triaging in Crashlytics simply quieted down after the engine swap.
On lower-spec Android devices (the Pixel 3a, older Galaxy A models), the rate of white-screen-on-resume also fell from eleven percent under JSC to about four percent. The most accurate way to describe the change is that the OS now has a little more headroom before it decides to kill the process.
Crash-Free Rate and the Pitfalls I Hit
Crash-free sessions moved from 99.4 percent to 99.7 percent. That is mostly a side effect of fewer memory crashes rather than Hermes itself being more stable.
There are pitfalls worth mentioning, because I burned half a day on the first one. Hermes uses a different source map lineage than JSC, and for the first few days Sentry showed me stack traces full of mystery function names. You need to bake "upload the Hermes symbol map produced by hermesc" into Step 0 of your release pipeline, otherwise production debugging effectively stalls.
The second one is Intl. The output of toLocaleString("ja-JP", ...) differs subtly between JSC and older Hermes builds — at one point my eCPM and pricing displays lost their thousands separators. The version of Hermes bundled with Expo SDK 53 fixes this, but if you are mixing in projects on SDK 51 or earlier, watch the formatting in your live screens carefully.
What Two Months Taught Me
The honest conclusion after two months is that Hermes is not a "make-it-fast switch." It is structural work that tidies up memory and startup. To turn that into something users feel, you still need to sequence ad and billing SDKs deliberately, lazy-load images, and design your splash screen so it stops being a delay.
Even so, the Android memory side moved the dial in places that matter. About sixty percent of my 50 million downloads sit on Android, and Day 1 retention on the migrated apps lifted by two points. In AdMob's monthly eCPM terms, that lands as a five to eight percent uplift — small per user, meaningful across the catalog.
The next thing I want to try is shrinking the bytecode payload when shipping through EAS Update OTA. I am reworking transformer.minifierPath in metro.config.js to see if I can take another fifteen percent off the delivered size. I will write up that result in a month. If you are running Rork with Hermes in production too, I hope these notes save you a few hours.