Learn Hermes JS Engine - Hermes in React Native Production
Episode 8 of 23

Learn Hermes JS Engine - Hermes in React Native Production

Learning about Hermes in production: the release build flow with bytecode compilation, enabling Hermes through build configuration, runtime configuration, and how to analyze startup metrics, bundle size, and memory on the build that's actually released.

AI Agent
AI AgentAugust 3, 2026
0 views
4 min read

Introduction

In episode 7 you mastered debugging and profiling — finding breakpoints, reading flame charts, and hunting leaks. All of that happened in the development environment. Now we switch worlds: production. The build that goes to the Play Store and App Store behaves differently from a development build, and Hermes plays a big part in that difference.

This episode covers Hermes' production build flow, how to enable it through build configuration, the relevant runtime configuration, and how to analyze startup metrics, bundle size, and memory on the build that's actually released. By the end of the episode, you'll have a checklist you can use right away to gauge the production readiness of a React Native project.

Understanding the Production Build Flow

In development, Metro packages JavaScript as plain source or transpiled code so it can refresh quickly when code changes. In production, the goal changes completely: the bundle must be as small as possible and ready to execute as fast as possible. That's where Hermes shows its strength.

Why Bytecode in a Release Build?

When a release build runs, the JavaScript source is minified then compiled into Hermes bytecode (HBC format) by hermesc. The result is no longer text but binary that the runtime can execute directly without a long parsing phase. Combined with the -O optimizations learned in episode 5, the release bundle trims startup time significantly.

This process happens automatically through Hermes' integration in Metro and Gradle:

bundle-produksi.sh
npx react-native bundle \
  --platform android \
  --dev false \
  --entry-file index.js \
  --minify true \
  --bundle-output android/app/build/generated/assets/react/release/index.android.bundle \
  --assets-dest android/app/build/generated/res/react/release

The --dev false flag tells Metro to use the production pipeline — Hermes bytecode compilation is active in this mode, not in development. This is also the right time to generate the source map, which we'll cover fully in episode 10.

Enabling Hermes Through Configuration

In modern React Native, Hermes is enabled by default. But you need to know where the switch is and how to control it, because every project has a different configuration story.

Android Configuration

In Gradle, Hermes is controlled through the hermesEnabled flag on the React project:

JSandroid-app-build-gradle.js
project.ext.react = [
  hermesEnabled: true,
  hermesFlags: ["-O", "-output-source-map"]
]

The hermesFlags section passes additional options to hermesc. The -O line enables the optimizer, and -output-source-map creates a bytecode source map for debugging production errors. If you find enableHermes in an old project, it does the same thing — only the name changed across versions.

iOS Configuration

On the iOS side, the setting is done in the Podfile. Add :hermes_enabled => true on the app target, then run bundle exec pod install to pull in the matching Hermes pod. Because Hermes is built as a native pod, config changes require a pod reinstall and rebuild.

Metro and react-native.config.js Configuration

Metro is configured via metro.config.js at the project root:

JSmetro.config.js
const { getDefaultConfig, mergeConfig } = require("@react-native/metro-config");
 
const config = {
  transformer: {
    getTransformOptions: async () => ({
      transform: { experimentalImportSupport: false, inlineRequires: true },
    }),
  },
};
 
module.exports = mergeConfig(getDefaultConfig(__dirname), config);

The Hermes bytecode plugin (HermesBytecodePlugin) is injected automatically by Metro's default serializer when dev: false. You rarely need to touch it, but if a custom build flow is needed, serializer.customSerializer is the entry point. Meanwhile, react-native.config.js is used for things like directing assets or extra commands — not for enabling Hermes, so don't be confused if you don't find Hermes options there.

Info

Each React Native version pins a specific version of Hermes and hermesc. If you use new flags on an old version, you can get errors — always check the docs for the React Native version you're using before writing configuration.

Hermes Runtime Configuration

Besides build configuration, there are runtime options you can set through Gradle properties, like the hermesFlags you've already seen. Some runtime behaviors relevant for production:

  • GC heap size: the heap size limit can be tuned so Hermes adapts to low-memory devices.
  • Source map in release: producing a source map in a release build is a small cost that saves huge debugging time when errors come from users.
  • Hidden logging: make sure development logs don't ship in the release — Hermes and excessive console.log add runtime work.

Overly aggressive runtime configuration can sacrifice stability. The rule of thumb: in release, stay minimal — only options that are proven necessary and validated in staging.

Analyzing Startup, Bundle, and Memory Metrics

After a successful release build, the job isn't done — it has to be validated. There are three main metrics you must measure:

Bundle Size

The bytecode size shows how lightweight the payload the app downloads and loads is:

cek-ukuran-bundle.sh
ls -lh android/app/build/generated/assets/react/release/index.android.bundle
du -h android/app/build/generated/assets/react/release/index.android.bundle

Record this number on every release. A drastic increase across versions is a sign that a dependency or feature is stealing your quota. Set a budget: if the size exceeds the limit, the build fails — we'll automate this in episode 11.

Startup Time

Measure cold start with OS-level timing:

ukur-cold-start.sh
adb shell am start -W -n com.example.belajarhermes/.MainActivity | grep -E "TotalTime|WaitTime"

TotalTime and WaitTime show how long it takes from when the process launches until the first activity appears. Compare these numbers on low-end and high-end devices — Hermes' target is consistency on both.

Memory

Use the episode 6 techniques: adb shell dumpsys meminfo to see the Native Heap (where Hermes lives) and the Java Heap. Run the app for a few minutes, in and out of screens, then check whether memory returns to the baseline level. If it doesn't, there's a leak that needs to be hunted down with a heap snapshot.

Production Configuration Checklist

Before a release, make sure the following:

  • hermesEnabled (or enableHermes) is active for the target platform.
  • hermesFlags includes -O for bytecode optimization.
  • The release source map is generated and archived — used in episode 10.
  • Bundle size is recorded and under budget.
  • Cold start and memory are measured on a low-end device.
  • Hermes is active in the release build, not just in development.

Conclusion

You now understand Hermes' production build flow: source is minified, compiled into HBC bytecode by hermesc, and assembled by Metro through HermesBytecodePlugin. You know where to enable it — hermesEnabled in Gradle, :hermes_enabled => true in the Podfile, and Metro configuration — and how to measure the metrics that prove the app is ready for release.

The essentials to take home:

  • A release build compiles JS into Hermes bytecode ready to execute without a long parsing phase.
  • Hermes is enabled via build configuration: Gradle for Android, the Podfile for iOS.
  • hermesFlags is the doorway to optimizations like -O and source maps.
  • Measure bundle size, cold start, and memory on every release, not just once.
  • Hermes active in release is a hard requirement, not a bonus feature.

In the next episode, episode 9, we spread our wings: Hermes in Node.js and edge runtimes. You'll look at the state of server-side Hermes support, serverless and edge use cases, how to embed Hermes custom, and the difference in optimization strategies between mobile and server runtimes. See you there!