All articles
Mobile DevelopmentOctober 31, 2026 7 min read

Hermes vs JSC in 2026: When We Still Reach for JavaScriptCore on React Native

Hermes is the default React Native engine in 2026, and for most apps that's the right call. But we still ship a few projects on JavaScriptCore — here's exactly when and why.

Hermes has been the default React Native engine for a while now, and in 2026 the question isn't really "should I use Hermes?" — it's "is there any reason left to pick JavaScriptCore?" The honest answer, after shipping both across a few dozen apps, is: yes, occasionally. Not often, but the cases are real and they're worth knowing before you default-your-way into a late-stage regression.

What actually changed between the two engines

Hermes was built by Meta specifically for React Native: ahead-of-time bytecode compilation, a smaller memory footprint, and faster time-to-interactive on mid-range Android. JavaScriptCore (JSC) is Apple's engine, bundled on iOS forever and shipped on Android via a prebuilt binary. It's a general-purpose JIT engine optimized for long-running JavaScript — think Safari tabs, not cold app launches.

The practical differences we care about on a mobile team:

  • Startup: Hermes wins on cold start because bytecode is already compiled at build time. JSC has to parse and interpret, then JIT the hot paths.
  • Steady-state throughput: JSC's JIT can be faster than Hermes on long-running, compute-heavy workloads once it warms up.
  • Memory: Hermes is leaner in most apps we've measured, often meaningfully so on Android devices with 3–4 GB of RAM.
  • Debugging: Hermes integrates with the React Native DevTools frontend (Chrome DevTools protocol). JSC debugging exists but the tooling story in 2026 is clearly centered on Hermes.
  • Intl and locale: This is the one that still bites teams. More on it below.

The 2026 default: Hermes, and you probably shouldn't fight it

If you're starting a new app on React Native 0.76+ or a recent Expo SDK, Hermes is on by default and we leave it on by default. For a typical product app — screens, lists, forms, some animations, a bit of networking — Hermes gives you:

  • 200–400 ms faster cold start on mid-range Android, in our experience
  • Smaller release APK/IPA because the JS is shipped as bytecode
  • Lower memory ceiling, which matters when the OS decides who to kill in the background
  • First-class profiling via the sampling profiler and the new DevTools

If you're doing greenfield work, stop reading and ship Hermes. The rest of this article is for teams with a specific reason to question that default.

How to actually confirm which engine you're on

It's embarrassingly easy to think you're on Hermes and not be, especially after a migration or a config merge gone sideways. Drop this somewhere early in your app:

const engine =
  typeof HermesInternal === 'object' && HermesInternal !== null
    ? 'hermes'
    : 'jsc';

console.log('[engine]', engine);

In production, log it once to your analytics pipeline. We've caught more than one "why is our bundle 40% bigger than expected" ticket this way.

When we still reach for JSC

There are three categories of app where we've genuinely chosen JSC over Hermes in the last 18 months.

1. Apps that lean hard on Intl with full ICU data

Hermes ships with a slimmed-down Intl implementation. For most apps formatting a currency or a date in the user's locale, it's fine. But if you need:

  • Full Intl.Collator behavior across non-Latin scripts
  • Intl.Segmenter with grapheme-correct behavior across emoji and complex scripts
  • Rich Intl.RelativeTimeFormat or Intl.ListFormat output across dozens of locales
  • Consistent output between iOS and Android for compliance reasons

...you will hit edges. The Hermes team has been closing gaps steadily, but if you're building a translation tool, a reader app, or anything that promises locale-perfect typography, JSC on iOS plus a polyfill strategy on Android is still the more predictable path. We shipped a reading app in 2025 that moved back to JSC specifically because a QA team in Japan kept finding mixed Hiragana/Katakana sort order bugs that Hermes couldn't reproduce consistently across versions.

2. Heavy, long-running compute on the JS thread

If your app runs sustained JavaScript workloads — a chess engine, a rules evaluator for a tabletop companion app, a client-side search index over 50k rows — JSC's JIT can pull ahead once it warms up. Hermes is an interpreter with inline caches; it's deliberately not a JIT because JIT engines are harder to audit and ship inside app store sandboxes.

We benchmark these cases per-app. In one scheduling app we worked on, a worst-case recomputation that took around 180 ms on Hermes landed closer to 110 ms on JSC after warm-up, on the same iPhone. That's not a reason to switch globally, but it was a reason to move that specific workload off the JS thread entirely — either into a native module, a worklet via Reanimated, or a WebAssembly runtime. The right answer wasn't "swap the engine," it was "stop asking the engine to do this."

If your Hermes performance problem is a hot loop on the JS thread, swapping to JSC is a tactical patch. The strategic fix is almost always to get that work off the JS thread.

3. You depend on a native module that still assumes JSC

This is rarer than it used to be, but we still see it with older in-house SDKs — usually something that reaches into the JSI with assumptions about the underlying VM. If you can't rebuild or replace that module in your release window, pinning to JSC is a reasonable short-term call while you plan the migration.

The decision matrix we actually use

When a client asks us which engine to ship, this is roughly how the conversation goes:

SituationOur pick
New app, standard product featuresHermes
App with heavy animations and gesturesHermes (and move work to Reanimated worklets)
App targeting low-end Android devicesHermes, every time
App with strict Intl requirements across many localesJSC + polyfills, or Hermes with a thorough QA plan
App with sustained CPU-bound JS workloadsHermes, plus a plan to move the hot path to native or WASM
Legacy codebase with a JSC-coupled native moduleJSC, with a migration ticket on the backlog

Notice how few rows pick JSC. That's the honest shape of 2026.

Flipping the switch (and the things that will bite you)

If you've decided to try JSC on an existing Hermes app — or vice versa — the toggle itself is one line. For a bare React Native project in android/gradle.properties:

hermesEnabled=false

And in ios/Podfile:

use_react_native!(
  :path => config[:reactNativePath],
  :hermes_enabled => false
)

For Expo, it's in app.json:

{
  "expo": {
    "jsEngine": "jsc"
  }
}

What the toggle doesn't do:

  • Rebuild your source maps strategy: Hermes uses .hbc bytecode and a compose source map. If you've wired Sentry or Bugsnag to the Hermes map format, you need to update that upload step when switching to JSC, or your crash reports become useless.
  • Reset your perf budget: Startup, memory, and bundle size will all shift. Re-run your release-gate benchmarks on real devices, not simulators.
  • Fix OTA updates: EAS Update ships different artifacts per engine. Make sure your update channel is actually serving the right ones. We've written about EAS Update rollouts in the blog if you want the deeper version.
  • Guarantee behavioral parity: Date parsing, regex edge cases, and Intl output can all differ. Run your full E2E suite against the swapped engine before you even think about a staged rollout.

A quick sanity script before you ship

We keep a smoke test that runs after every engine change. It's not sophisticated — it's a screen that renders formatted dates, numbers, currencies, and a few collator sorts in a half-dozen locales, then diffs the output against a golden file per engine. It has caught more real bugs than any benchmark.

Where we'd start

If you're on Hermes and happy, do nothing. Hermes is the right default and the gap keeps widening in its favor for most apps.

If you're debating a switch, do this in order:

  1. Confirm which engine you're actually on in production, using the snippet above.
  2. Define the specific regression or requirement that's pushing you to consider JSC. If you can't name it in one sentence, Hermes stays.
  3. If the pain is CPU-bound, prototype moving that work to a worklet, a native module, or WebAssembly before touching the engine config.
  4. If the pain is Intl, write a locale smoke test and run it on both engines on real devices.
  5. Only after all of that, ship an internal build on the alternate engine and measure startup, memory, and bundle size against your current baseline.

And if you want a hand pulling that benchmark together on a live app, our mobile team does this kind of engine audit as a one-week engagement. Most of the time we leave the engine alone and fix something upstream instead — which is usually the right outcome anyway.

#React Native#Hermes#Performance#Mobile Development#Expo

Want a team like ours?

72Technologies builds production software for the kind of teams who actually read this blog.

Start a project