Hermes is an engine choice, not an API. Nothing to migrate off, no pattern to rewrite. What changes sits earlier than any of your code: the shape your bundle is in by the time it reaches the device.
#The old way
Before Hermes became the default engine, apps shipped with JavaScriptCore (JSC). JSC parses and JIT-compiles the bundle's raw JS source on-device on every cold start, so startup time scales with how much of that source has to be parsed before the first frame can render. The bundle you ship is roughly the size of your source.
#The new way
Metro compiles your bundle to Hermes bytecode (.hbc) at build time via hermesc, and the device loads that precompiled bytecode instead of parsing source.
Which engine you're on is checkable at runtime:
import { Platform } from 'react-native';
function isHermesEnabled(): boolean {
return typeof (global as any).HermesInternal === 'object' && (global as any).HermesInternal !== null;
}
function logEngineInfo() {
const engine = isHermesEnabled() ? 'Hermes' : 'JSC';
console.log(`Running on ${engine}, platform ${Platform.OS}`);
}
HermesInternal is a global the Hermes runtime injects, and checking for it is the standard way to detect the engine from JS at runtime.
#Why it matters
Cold start stops paying the JS parse and compile bill on-device. Bytecode is precompiled at build time.
The bytecode format is compact and the generational garbage collector is tuned for mobile, so most apps end up with a smaller heap and lower RAM use than they had on JSC. Release builds still get proper source maps for crash symbolication. Stack traces stay readable without raw source going anywhere near the device.
#Gotcha
Hermes bytecode is engine- and version-specific. An .hbc bundle built with one Hermes version isn't guaranteed to run on another.
If you inspect or diff production bundles, or reach for any bytecode-level tooling, pin your Hermes version to your React Native version rather than assuming portability across upgrades.