Rocket Forge Studio logo
Rocket Forge Studio
Web & product studio

Business & growth

React Native Metro Babel React Compiler Hermes Pipeline: How Your JS Becomes Native Runtime Code

Amir Behrouzi8 min read
  • React Native
  • Metro
  • React Compiler
  • Hermes
  • Build tools
Vertical pipeline diagram React Native Code to Metro to Babel to React Compiler to Transformed JS to Hermes in warm gold minimal tech style

When something feels slow in a React Native app, teams often blame Hermes—or "Metro is weird"—without naming the real stage. Your JavaScript does not go straight from a `.tsx` file into the engine. In a **typical Babel-based setup**, the **React Native Metro Babel React Compiler Hermes** path looks like this:

  • **React Native code** — your components, hooks, and platform files
  • **Metro** — resolve the module graph, transform workers, Fast Refresh, serialize the pack
  • **Babel** — JSX/TS and RN presets; host for most optional plugins
  • **React Compiler** — optional Babel-stage rewrite for automatic optimizations
  • **Transformed JS** — packager output Metro can ship (still JavaScript text)
  • **Hermes** — default engine; production paths often compile to Hermes bytecode

Other RN pipelines exist (different transform hosts, experimental Rust compilers on web-adjacent stacks, custom Metro configs). Treat the chain above as the common Babel-based mental model—not the only possible wiring. If you are debugging cold start, jank on the JS thread, or a React Compiler enablement, name the stage before you change the tool.

Stage 1 — React Native source

You write components, hooks, and platform files (`*.ios.tsx`, `*.android.ts`). Metro's job starts at the entry file and walks the module graph. Nothing is Hermes bytecode yet. Mistakes here—impure renders, giant trees, deep imports—still show up later as transform cost or runtime work.

Stage 2 — Metro: resolve, watch, bundle

**Metro** is the React Native bundler: resolve modules, transform workers, Fast Refresh, and serialize a pack for the device or packager. Expo and React Native docs treat Metro as the official path for mobile (and increasingly for Expo web) because it is tuned for Hermes profiles, not only static browser bundles.

When Metro feels "slow," ask which phase hurts: file watching, transform workers, or serialization. Those are different knobs than Hermes GC.

Stage 3 — Babel: make the graph runnable

Inside Metro's transform step, **Babel** (via `@react-native/babel-preset` / Expo's preset) turns modern JS/TSX into something the target engine can run: JSX, TypeScript strip, and React Native–specific plugins (including codegen hooks when needed).

Babel is also where most teams **plug in** experimental or optional transforms. That is why React Compiler on RN usually appears as a **Babel plugin** inside Metro—not as a separate CLI you run by hand every save. If your app uses a different transform host, the Compiler may attach elsewhere; confirm against your Expo/RN version docs.

Stage 4 — React Compiler (optional, build-time)

**React Compiler** sits in the Babel pipeline when enabled in a typical Babel-based setup. It applies **automatic optimizations** to components and hooks at build time—so you lean less on hand-written `useMemo` / `useCallback` and related manual tuning. On React Native, that can mean less JS-thread work *after* Hermes starts executing—if your code follows the Rules of React.

Important sequencing:

  • Compiler runs **before** you hand transformed JS to the engine
  • It does **not** replace Metro or Hermes
  • Enabling it without fixing Rules of React violations can ship silent bugs—pair with Compiler-aware lint when you can
  • Config usually lives in Babel/Metro options (and Expo presets expose a Compiler flag when supported)

Web toolchains are racing toward native Rust compilers (Oxc, Bun). On RN today, the mental model for most Babel-based apps remains: **Metro owns the graph; Babel hosts the Compiler plugin**—until your version documents another host.

Stage 5 — Transformed JavaScript

After Babel (and optional Compiler), you have **transformed JS** Metro can serialize: module wrappers, dependency map, source maps for DevTools. This is still text JavaScript—not Hermes bytecode. Dev builds often stay closer to this form for Fast Refresh and debugging.

Stage 6 — Hermes: execute (and often bytecode)

**Hermes** is the default JS engine on modern React Native. In production paths, Metro/build tooling can compile the bundle toward **Hermes bytecode** so startup skips parse/AST work on device. Hermes V1 became the default engine path in recent RN releases—faster execution without a separate "migrate off JSC" project for most apps already on Hermes.

If the app is slow *after* first paint, look at JS work and React render cost—not only whether bytecode was generated. If cold start is the complaint, verify bytecode and bundle size at this stage.

Why the pipeline order matters for teams

Mis-attributing stages wastes sprints:

  • **"Hermes is slow"** but the real cost is Babel+Compiler transform time in CI
  • **"Turn on React Compiler"** without Metro/Babel config—so nothing changes in the binary
  • **"Metro cache"** blamed for a Rules of React bug that only shows after Compiler rewrites
  • **Native rebuild** when the issue is pure JS transform and would fix with a packager restart

Agency and product teams that ship both web and RN should keep two maps: web may move Compiler into Oxc/Bun; a **typical Babel-based RN setup** still centers on **Metro → Babel → (Compiler) → JS → Hermes** until your RN/Expo version documents a different transform host.

Practical checklist

  • Draw the pipeline on the wiki for your app's exact Expo/RN version (note if it is Babel-based or another host)
  • Confirm where React Compiler is enabled (Babel plugin / Expo preset)—or document that it is off
  • Separate CI metrics: Metro transform time vs device cold start vs JS-thread interaction
  • When enabling Compiler, fix lint/Rules of React on a branch first
  • Use React Native DevTools with Metro+Hermes for network and JS debugging—not a random Chromium attach
  • After upgrades (e.g. Metro 0.87, RN 0.87), re-verify Compiler and Hermes defaults—the middle of the pipeline moves more often than the product UI

The takeaway

In a **typical Babel-based setup**, remember: **React Native code → Metro → Babel → React Compiler → transformed JS → Hermes**. Metro owns the graph. Babel hosts most transforms. React Compiler is an optional Babel-stage rewrite for **automatic optimizations**. Hermes runs what comes out—often as bytecode in production.

Name the stage before you change the tool. If you want a second pair of eyes on Metro/Compiler enablement for a client app, get in touch.

← All articles