The Hermes Engine in React Native: Why It Makes Apps Faster
AI generated
RN
native
React Native · Hermes · JSI · Mobile Performance
The Hermes engine in React Native
why it makes apps noticeably faster

The Hermes engine is the JavaScript runtime React Native has used by default since version 0.70, built to start apps faster and run with less memory. Instead of re-parsing JavaScript on every app launch, Hermes precompiles the code into bytecode at build time, a change that benefits older Android devices with limited memory the most.

14 min read Hermes · JSI · bytecode · garbage collection React Native 0.70+ · Expo · New Architecture

1. Why React Native needed its own JS engine

Until React Native 0.70, JavaScript ran by default on JavaScriptCore (JSC), the engine behind Safari. JSC is mature on desktop and iOS, but it was never optimized for the startup conditions of typical Android devices: limited memory, slower CPUs, and an app launch that requires parsing the entire JavaScript bundle every single time. Meta built the Hermes engine specifically for mobile devices with one clear goal: faster startup and lower memory usage, without requiring developers to change their code.

Since React Native 0.70, Hermes has been the default engine on both platforms, iOS included. That was a deliberate strategic shift: instead of using a general purpose engine built for every JavaScript environment, React Native gets a runtime tuned exactly to the constraints of mobile hardware. For teams targeting large user bases on older Android devices in growth markets, this is not a footnote, it is a decisive factor for conversion and app store ratings.

The core of the difference is not the language itself but how and when JavaScript gets turned into bytecode. That exact mechanism is the subject of the sections below, from compilation through startup and garbage collection to practical setup and the limits of the Hermes engine.

2. AOT bytecode compilation: the heart of Hermes

The central difference between the Hermes engine and classic JS engines is ahead-of-time (AOT) compilation. Instead of parsing JavaScript source at runtime and translating it into machine code via a just-in-time (JIT) compiler, Hermes compiles the JavaScript code into a compact bytecode format during the build process, using the `hermesc` tool. The shipped app no longer contains JS source text at all, only directly executable bytecode.

This approach shifts expensive work from the user's device to the developer's build pipeline. Parsing, syntax analysis, and most optimization decisions happen once at build time, not on every single app launch across potentially thousands of different devices. For an engine that has to serve millions of app launches on very heterogeneous hardware, this is a fundamental efficiency gain over classic JIT behavior.

The Metro bundler integration makes this step invisible to developers: during a production build, the JavaScript bundle is automatically run through `hermesc` and embedded in the app as a compiled bytecode file. Developers keep writing normal JavaScript or TypeScript, the conversion to bytecode is a build artifact that only appears at release build time.


# Hermes bytecode compiler runs automatically during release builds
# Manual invocation for inspection purposes:
node_modules/react-native/sdks/hermesc/osx-bin/hermesc \
  --emit-binary \
  -O \
  -out index.android.bundle.hbc \
  index.android.bundle

# Inspect bytecode size vs raw JS bundle size
ls -lh index.android.bundle index.android.bundle.hbc

3. App startup in practice: parse cost versus bytecode loading

To understand why the Hermes engine speeds up app startup, it helps to look at the classic JSC startup sequence: the engine loads the JS bundle as text, tokenizes it, builds a syntax tree, runs semantic checks, and only then begins execution. For larger bundles, common in production React Native apps with many screens and libraries, this parsing step makes up a noticeable share of overall Time to Interactive (TTI).

Hermes skips this step almost entirely. Since the bytecode is already available, the engine only needs to load and interpret it at startup, without first converting text into an intermediate representation. The result is a noticeably shorter gap between tapping the app icon and reaching an interactive first screen, especially on devices with slower CPUs, where parsing weighs proportionally the heaviest.

This effect is not evenly distributed across device classes: on powerful, current iPhones, the difference is often barely perceptible, because modern JSC JIT compilers parse very quickly there. On budget Android devices with little RAM and weak CPUs, common across many growth markets, the Hermes engine makes the biggest difference, often with noticeably shorter cold start times.

4. Memory footprint and garbage collection

Alongside startup, memory usage is the second major advantage of the Hermes engine. Hermes uses a generational garbage collector that groups objects into different memory areas (generations) based on their expected lifetime. Short-lived objects, the kind created on every React component render, are managed in a small, quickly searchable young memory area and efficiently reclaimed without scanning the entire heap.

This design reduces two concrete problems in the mobile context: out-of-memory crashes on devices with little RAM, and GC-related jank, perceived as visible stutter in the UI. Since mobile operating systems aggressively kill apps under memory pressure, a smaller, efficiently managed heap is directly tied to better app stability, not just abstract performance numbers.

In practice this shows up especially in list-heavy screens with many reused cells, such as social feeds or product catalogs. Without efficient garbage collection, fast scrolling accumulates many short-lived intermediate objects, whose cleanup on a less mobile-optimized engine can cause visible frame drops. Hermes' generational strategy keeps exactly these patterns performant.

5. Enabling and verifying Hermes

Since the Hermes engine has been the default since React Native 0.70, most new projects need no additional configuration. In older projects, or for manual control, the engine can be set explicitly through `gradle.properties` for Android and the Podfile for iOS. In Expo projects, a single entry in `app.json` is enough to set the engine per platform.

Verification after every build matters: a simple check against the global `HermesInternal` object reliably shows at runtime whether the app is actually running on Hermes or has silently fallen back to the classic engine, for example after a broken configuration change.


{
  "expo": {
    "name": "MyApp",
    "jsEngine": "hermes",
    "android": {
      "jsEngine": "hermes"
    },
    "ios": {
      "jsEngine": "hermes"
    }
  }
}

// Verify Hermes is actually active at runtime
import { useEffect } from 'react';

function EngineCheck() {
  useEffect(() => {
    // global.HermesInternal only exists when Hermes is the active engine
    const isHermes = () => !!global.HermesInternal;
    console.log('Running on Hermes:', isHermes());
  }, []);

  return null;
}

export default EngineCheck;

On the native side, it is worth checking `android/gradle.properties` (`hermesEnabled=true`) and the iOS Podfile (`:hermes_enabled => true`) if a project was migrated from an older React Native version. After any change to these files, a full clean build is required, since the engine choice is compiled into the native binary at build time and a simple JS reload is not enough.

6. JSI and the New Architecture

Another reason the Hermes engine is so central to React Native today is its tight integration with the JavaScript Interface (JSI). JSI replaces the old, asynchronous bridge, through which JavaScript and native code used to communicate exclusively via serialized JSON messages. With JSI, native objects can be held as direct references in JavaScript, enabling synchronous calls between both worlds without serialization overhead.

Hermes was designed with JSI compatibility from the start, making it ideally suited for TurboModules and the Fabric renderer of the New Architecture. While JSC had to be retrofitted for JSI support, the tight integration in Hermes is a native part of its design, which shows up as lower overhead on frequent cross-language calls.

For teams migrating to the New Architecture, the combination of Hermes and JSI is not an optional detail, it is the foundation on which TurboModules actually perform well. Anyone still on JSC loses part of the efficiency gains JSI is meant to provide, because the bridge between engine and native code requires more adaptation work there.

7. Debugging Hermes apps

Debugging with the Hermes engine works through the Chrome DevTools protocol, which Hermes natively supports. Developers can set breakpoints, inspect variables, and walk the call stack, similar to web development. The big difference from a classic remote debugging session: the JavaScript code keeps running directly on the device or simulator, not in a separate Chrome instance, which makes timing behavior more realistic.

For production crashes, symbolication via source maps is essential. Since shipped code exists as bytecode, stack traces without source maps are practically useless, showing only bytecode offsets instead of readable function names and line numbers. A common mistake in CI/CD pipelines is failing to consistently upload the source maps generated by `hermesc` to the crash reporting service, which renders production crash reports unusable.

A solid setup automatically uploads the source map file to the crash reporting tool on every release and links it to the corresponding build number. Without that step, even the most stable Hermes engine configuration stays hard to debug for real production errors, because the actual root cause remains hidden inside the bytecode.

8. Limitations of Hermes and a look at Static Hermes

The Hermes engine is not the superior choice in every scenario. Early versions were missing some `Intl` API functionality for internationalization, largely retrofitted since, but this can still matter on very old Hermes versions. Some debugging workflows, especially ones heavily reliant on specific JSC tooling, do not work identically with Hermes, even though the Chrome DevTools protocol covers most use cases.

The genuinely exciting development is Static Hermes, a project announced by Meta that precompiles JavaScript not just into bytecode but directly into native machine code. This would eliminate the runtime interpretation step entirely and reach performance characteristics close to classic JIT compilation, without its runtime overhead at app startup.

For most production apps today, the standard Hermes engine remains the right, well tested choice. Static Hermes is a glimpse of the next evolutionary step, but not yet a replacement for the currently shipped, stable bytecode variant running in millions of production React Native apps.

9. Hermes compared: JSC, V8 and Hermes

Choosing a JavaScript engine has direct consequences for startup time, memory usage, and debugging experience. The table below compares the three relevant engines in the mobile React Native context.

Criterion Hermes JavaScriptCore (JSC) V8
Compilation model AOT bytecode at build time JIT at runtime JIT at runtime
Startup (low-end Android) Very fast Slower due to parsing Not used in production RN
Memory usage Low, generational GC Higher Higher, tuned for desktop
JSI integration Designed natively Retrofitted Not relevant to RN
Default in React Native Yes, since RN 0.70 Previous default No

The table makes clear why Meta chose Hermes as the default: the combination of AOT bytecode, lower memory usage, and native JSI support fits exactly what mobile apps need, while JSC plays to its strengths more in browser and desktop environments, the context it was originally built for.

Mironsoft

React Native performance audits and app optimization

Want professional startup and memory optimization?

We audit your React Native app's engine configuration, bytecode setup, and memory behavior, making sure the Hermes engine delivers its full performance potential, from the build pipeline to crash reporting.

Engine audit

Review of Hermes configuration, bytecode build, and source map pipeline

Startup optimization

Measuring Time to Interactive and improving it on low-end devices

Crash reporting

Source map upload and symbolication for reliable production diagnostics

10. Summary

The Hermes engine speeds up React Native apps because it moves the most expensive step of an app launch, parsing JavaScript, off the user's device and into the build pipeline. AOT bytecode compilation, a generational garbage collector, and native JSI integration together deliver shorter Time to Interactive, lower memory usage, and less GC-related jank, especially on low-end Android devices that make up the largest share of the global user base.

Practical setup is rarely a separate task anymore since React Native 0.70, because Hermes is the default. What still matters is consistent verification via `HermesInternal`, a working source map pipeline for crash reporting, and awareness of the few remaining limitations of the engine. Static Hermes already hints at the next evolutionary step, one that could eliminate the interpretation step entirely.

The Hermes Engine in React Native: Key Takeaways

AOT bytecode

JavaScript is compiled into bytecode with `hermesc` at build time, not parsed on every app launch.

Faster startup

Shorter Time to Interactive, most noticeable on low-end Android devices with weak CPUs.

Generational GC

Short-lived objects are managed efficiently, fewer OOM crashes and GC-related jank.

JSI & New Architecture

Native JSI integration is the foundation for performant TurboModules and the Fabric renderer.

11. FAQ: The Hermes Engine in React Native

1What is the Hermes engine?
A JS engine built by Meta that compiles JavaScript into bytecode at build time instead of parsing it at runtime. Default since React Native 0.70.
2Why is Hermes faster than JSC?
Parsing happens at build time, not startup. Only finished bytecode needs loading at startup, saving time on slower devices.
3How do I check if Hermes is active?
Check global.HermesInternal: if it exists, Hermes is running. If missing, the app fell back to the classic engine.
4Do I need to adapt code for Hermes?
Usually not. Modern JavaScript features are largely supported, earlier Intl API gaps have mostly been closed.
5Why does Hermes improve memory usage?
A generational garbage collector manages short-lived objects efficiently, reducing OOM crashes and GC-related jank.
6What is JSI and its link to Hermes?
JSI replaces the old asynchronous bridge with direct references. Hermes was designed JSI compatible from the start, ideal for TurboModules and Fabric.
7Why are crash reports unreadable?
Without source maps, crash reports only show bytecode offsets. The source map file must be uploaded to the crash reporting service on every release.
8What is Static Hermes?
An announced project that compiles JavaScript directly into native machine code instead of bytecode, eliminating the runtime interpretation step.
9How do I enable Hermes in Expo?
Set "jsEngine": "hermes" in app.json, globally or per platform. Since React Native 0.70 it is the default anyway.
10Where does Hermes benefit the most?
On low-end Android devices with limited RAM and weak CPUs. On powerful iPhones, the difference is often barely noticeable.