Technical Guide, Part 2: Integrating Hermes With React Native

The latest React Native 0.64 release adds support for the Hermes engine on iOS, and this is a direct result of our intensive work with teams at Facebook and Microsoft. We share the journey to Hermes on iOS in a series of four pieces:

While the first two introduce Hermes and give details about running Hermes in production, the last two offer solid technical insights based on the implementation done. They’re actually one two-part guide.

If you prefer to dig the code yourself, here’s my PR to React Native. Otherwise, fasten your seatbelts and you’re in for another technical ride!

Integrating Hermes with React Native

After Hermes was compiled for both iOS devices and simulators, and packaged into a Pod (a CocoaPods name for a dependency), it was time to integrate it to React Native. Thankfully, Eloy Durán and Microsoft team were here before as well! Their work on enabling Hermes in their macOS fork not only made it easier to compile Hermes for iOS, as discussed in the first part of the guide, but also made it easier to integrate Hermes with React Native. 

By upstreaming some of their work from their fork, they paved the way for the integration and definitely made my life much easier! 

For more context, you may want to check this PR to React Native macOS that captures the entire work that Eloy Durán did to bring Hermes to Apple computers.

Where to start the integration process

In order to execute the JavaScript code inside your application, React Native needs an instance of a JSExecutor. As the name suggests, it is responsible for executing the JavaScript code. On top of that, it provides additional API methods to allow interaction with native modules and synchronizing asynchronous actions, just to name a few.

Executing JavaScript code graph

As of today, there are two JSExecutors available inside React Native. The first one, which you may be most familiar with, is a JSCExecutor that executes your JavaScript code with the JavaScript Core engine. The other one uses Hermes and is called HermesExecutor. React Native creates an instance of an executor via an ExecutorFactory.

You’re probably wondering why we have two executors available within React Native if, at least at the time of writing this step-by-step technical guide, there was only JavaScript Core available as the engine on iOS.

The answer is simple - the majority of the core React Native code is written in C++. That makes it easier to share core between Android and iOS, when possible. To answer the question from the previous paragraph, JSCExecutorFactory is a factory written in C++ that is shared between iOS and Android. HermesExecutorFactory is a factory written in C++ that is exclusive to Android, and located inside the ReactAndroid folder as a result.

When to use Hermes over JSC

As you can see, at the time of starting Hermes integration for iOS, a lot of important core pieces were already in place. Thanks to that cross-platform approach of React Native at its lowest core level, adding support to Hermes on iOS was faster and smoother due to pre-existing code that was already running on Android. (There were a few additional places with Hermes and I will highlight them later in this article.)

In other words, moving HermesExecutor outside of ReactAndroid folder to a shared folder and including it in both iOS and Android builds was already part of the success. This is exactly what happened in this PR.

Once this was done, the next step was to activate that mechanism - teach React Native when to use Hermes over JSC. Our approach was to make it as smooth as possible by reducing the amount of steps to be done by the developer to a bare minimum. I am proud to say we have managed to reduce it to exactly one step. And that’s what I have described in the next step.

How to activate the engine

React Native on iOS uses CocoaPods quite extensively to automate the process of installing native dependencies as well as configure some of the project settings. This is done via a special function called use_react_native which is embedded in your Podfile.

In order to turn on Hermes on iOS, all you have to do is just one step - set hermes_enabled to true where you have your use_react_native function:

use_react_native! (
:path => config[:reactNativePath],
# to enable hermes on iOS, change `false` to `true` and then install pods
:hermes_enabled => true
)

Then run the <rte-code>pod install<rte-code> command once again. This will install any new native dependencies you have added, and reconfigure the project with the latest settings in mind. In this particular example, it will add the Hermes framework to your project.

Note: If you’re interested in how Hermes framework is generated and what lies inside, please check the previous part of this guide, where we go through the same process of generating Hermes framework step by step.

Unlike any other native code you may have worked with, there is no npm install or yarn install to do. This is because Hermes is already included with every React Native release and there is no need to install any additional packages to set things up.

Since the presence of Hermes binaries dictates whether it should or should not be used (based on your preferred setting in a Podfile), we can rely on this in all other places where we need to choose the right engine:

#if __has_include(<hermes/hermes.h>)
#define RCT_USE_HERMES 1
#endif

This macro will set a project-wide variable RCT_USE_HERMES if, and only if, Hermes headers are included in the project. As we know from the previous paragraph, such scenario will be true in cases when users opted-in to use Hermes.

Based on this behavior, we can now choose either Hermes or JSC executor (as mentioned at the beginning of this guide), depending on the presence of RCT_USE_HERMES.

First of all, we can choose which file to import in our file:

#if RCT_USE_HERMES
#import "HermesExecutorFactory.h"
#else
#import "JSCExecutorFactory.h"
#endif

and then initialize the proper executor factory:

#if RCT_USE_HERMES
executorFactory = std::make_shared<HermesExecutorFactory>(installBindings);
#else
executorFactory = std::make_shared<JSCExecutorFactory>(installBindings);
#endif

Thanks to that mechanism, RCTCxxBridge can choose the right executor factory, depending on whether Hermes is available or not. Thanks to the pre-existing abstraction within React Native that separates the engine from the rest of the architecture, no further changes were required in order to actually switch the engine itself.

How it works in practice

As soon as you turn on the demo application, you will see Hermes engine information at the top of your welcome screen. Even though we didn’t do any changes around React Native components, they are already able to detect that we’re running Hermes engine.

This is because the <NewAppScreen> is a cross-platform component and is actually running on Android too; Hermes has been out there in production for quite some time. That’s just another benefit of a cross-platform architecture.

The same thing applies to other pieces such as Flipper. I could go into details on how it works and what are its greatest benefits, but I guess there has to be something I leave for you to explore further! 

Try Hermes yourself

You can start building mobile applications with Hermes on both platforms as of today! The results of our work are available as a part of recently published React Native 0.64. You can learn more about it and how to turn it on in my post on the official React Native blog!

Can’t wait to see what you build with Hermes and how it turns out for you in production! While we continue our benchmarking, please share your findings and feelings on Twitter - make sure to tag me so that we can celebrate together!

Find out more

As part of The React Native Show, we released a podcast episode fully dedicated to Hermes: React Native 0.64 with Hermes for iOS. Check it out to learn more about Hermes on iOS and MacOS from Facebook and Microsoft engineers.

‍

‍

Insights

Learn more about Hermes

cover

Dec 18Article

Why You Don’t Have to Minify JavaScript Code in React Native Apps

React Native skips JavaScript minification in production because Hermes handles dead code elimination at the bytecode level. Here’s how and why it works.

cover

Dec 4Podcast

From Static Hermes to Hermes V1: The Road to Default

Nicola Corti (Meta) explains the evolution of Static Hermes into Hermes V1, the performance boosts it ships, and the (experimental) path to making it the new default engine.

cover

Nov 6Live Stream

Hermes V1: What It Is, What It Isn’t, and What’s Next

Łukasz Chludziński, Szymon Chmal, and Matt Hargett unpack Hermes V1, covering what’s new, what’s changing, and what’s ahead for React Native performance.

cover

Dec 4Article

How to Optimize Your Android App Startup Time With Hermes

Learn how to achieve better performance and optimize Android app startup time with Hermes.

cover

May 26Article

Technical Guide, Part 1: Compiling Hermes for Apple Platforms

Want to use Hermes in your React Native app on iOS? Find out how to compile Hermes for Apple platforms. Check the first part of our expert technical guide.

cover

May 11Podcast

React Native 0.64 With Hermes for iOS

Discover how Hermes boosts React Native performance on iOS and macOS, with insights into AOT, bytecode precompilation, and the future of Hermes support.

cover

Apr 21Article

Hermes Performance on iOS

Wondering how the Hermes engine performs on iOS? We tested it on a React Native mobile app. Check the results and see how it compares to JSC.

cover

Apr 14Article

Bringing Hermes to iOS in React Native 0.64

Hermes is now available for iOS as well. Find out more about this JS engine - check the first part of our series on bringing Hermes to iOS.

cover

Mar 19Tutorial

The Hard Way vs. The React Native AI SDK Way: Stop Writing Custom Modules for Every Model

Learn how React Native AI uses a unified provider model to switch between cloud and on-device LLMs without rewriting application logic, enabling reliable AI experiences online and offline.

cover

Mar 12Tutorial

Build Smarter Apps: Tool Calling & AI Orchestration Explained

Learn how on-device models in React Native can call tools, interact with external APIs, and return structured outputs you can use directly in application logic.

cover

Mar 5Tutorial

MLC LLM + React Native: On-Device AI Without the Pain

Learn how to run third-party on-device LLMs in React Native using MLC LLM. Choose your models, run them efficiently across platforms, and keep a unified JavaScript API with React Native AI.

cover

Feb 24Tutorial

Intelligent Fallbacks with Apple Intelligence in React Native

Learn how to use Apple’s built-in Foundation Models in React Native apps, reduce memory usage, and enable fast, fully on-device AI with React Native AI Apple.

cover

Feb 10Tutorial

What Is the React Native AI SDK? A Complete Intro & Quickstart

Learn why on-device AI matters for React Native apps, how local LLMs behave offline, and what problems React Native AI is designed to solve from day one.

cover

Jan 29Tutorial

Implementing an Android TurboModule with Kotlin

Learn how to implement a React Native TurboModule on Android, from the TypeScript spec and Codegen to the Kotlin implementation and package registration.

cover

Jan 19Tutorial

Working with Different Threads in Swift TurboModules

Learn how JavaScript, native module, background, and main threads interact in React Native TurboModules, and how to use GCD queues to run async work safely in Swift.

cover

Jan 9Tutorial

How to Add Type-Safe Constants to Swift TurboModules

Learn how to expose typed, immutable constants from a Swift TurboModule using Codegen, and get full end-to-end type safety between native and JavaScript.

cover

Dec 30Tutorial

Adding Event Emitters to Your TurboModule in Swift

Learn how to emit native events from a Swift TurboModule and subscribe to them in JavaScript, including proper memory management and Codegen integration.

cover

Dec 22Tutorial

Writing Your First TurboModule in Swift

Learn how to build a fully working TurboModule in Swift, integrate it with Codegen, and bridge it through Objective‑C inside an Expo app.