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.

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.
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.
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:
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/releaseThe --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.
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.
In Gradle, Hermes is controlled through the hermesEnabled flag on the React project:
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.
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 is configured via metro.config.js at the project root:
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.
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:
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.
After a successful release build, the job isn't done — it has to be validated. There are three main metrics you must measure:
The bytecode size shows how lightweight the payload the app downloads and loads is:
ls -lh android/app/build/generated/assets/react/release/index.android.bundle
du -h android/app/build/generated/assets/react/release/index.android.bundleRecord 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.
Measure cold start with OS-level timing:
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.
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.
Before a release, make sure the following:
hermesEnabled (or enableHermes) is active for the target platform.hermesFlags includes -O for bytecode optimization.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:
hermesFlags is the doorway to optimizations like -O and source maps.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!