skip to content
Andrei Calazans

React Native Navigation Benchmarks: What's the Expo Tax?

/ 3 min read

Part of the series. The deep dive showed the Hermes profiler attributing up to 241 ms of JS-thread CPU to @expo/expo-modules-core at startup. Here I run the actual control to separate the real Expo overhead from the noise.

Setup: two minimal apps rendering the identical Home screen — no navigation, no profiling instrumentation. bare-min: bare RN 0.85, zero Expo. expo-min: Expo SDK 56 blank app. Cold start = OS Displayed; RAM = dumpsys meminfo PSS at Home. Median of 5 runs. (experiments/ in the repo.)

What every Expo app ships

Even a blank Expo app autolinks 10 native modules — the same 10 appear in all three Expo apps in the main comparison:

Expo baseline — present in every Expo app:

expo · expo-modules-core · expo-asset · expo-constants · expo-file-system · expo-font · expo-keep-awake · expo-status-bar · @expo/dom-webview · @expo/log-box
AppExpo deps declaredNative Expo modules autolinked
expo-min (blank)110
React Navigation210
navigation router210
Expo Router915
bare RN apps00

The fixed Expo core tax

Metricbare-minexpo-minExpo tax
Cold start — Displayed (ms)293329+36
RAM — PSS at Home (MB)67.079.6+12.6
APK size (MB)49.565.7+16.2
Cold start (ms)
bare-min
293
expo-min
329
RAM — PSS at Home (MB)
bare-min
67.0
expo-min
79.6

~36 ms, ~13 MB RAM, ~16 MB APK. That’s the cost of expo-modules-core plus the 10 baseline modules. For most apps that’s a reasonable price.

What each extra module adds

Starting from expo-min, I added one module at a time — installed and autolinked, but not actually used.

Added moduleΔ cold startΔ RAM PSSClass
expo-device+3 ms~0constants only
expo-haptics+3 ms~0methods only
expo-clipboard~0+0.2 MBmethods only
expo-linear-gradient~0+0.2 MBnative view
expo-image~0+1.7 MBnative view + Glide
expo-blur+12 ms+0.5 MBnative view
expo-notifications+11 ms+2.7 MB+1 transitive module
expo-video+16 ms+5.9 MBmedia3 SDK
expo-camera+25 ms+5.1 MBCameraX SDK
Marginal RAM cost — idle, not in use (PSS, MB)
expo-device
~0
expo-haptics
~0
expo-clipboard
0.2
linear-gradient
0.2
expo-blur
0.5
expo-image
1.7
expo-notifications
2.7
expo-camera
5.1
expo-video
5.9

Simple modules (device, haptics, clipboard) are essentially free at idle. Even native-view modules add only 0–2 MB. Only heavy native SDKs — Camera (CameraX), Video (media3) — show a meaningful cost, and even then it’s ~15–25 ms and ~5–6 MB while unused. expo-modules-core initializes lazily, so the registry itself is cheap.

Cold-start deltas for light modules are inside run-to-run noise (±~15 ms on this device). PSS is the more reliable per-module signal.

Why the profiler blames 100–240 ms on @expo

The deep dive showed the profiler attributing large JS-thread CPU to @expo:

AppJS CPU blamed to @expo + expo-modules-core
React Navigation~118 ms
navigation router~185 ms
Expo Router~241 ms

These numbers scale with each app’s total native-module traffic, not with a fixed Expo overhead. In an Expo app every native-module call goes through expo-modules-core’s registry, so the profiler labels the app’s own UIManager calls, getConstants calls, and view-manager fetches as @expo. The same calls in a bare RN app show up as react-native/Native. The part that is truly extra — the registry/proxy indirection itself — is the ~36 ms measured above.

Data: perf-results/expo-cost/ in the StateOfReactNativeNavigation repo. Reproduce with perf-tooling/scripts/quick-coldstart.sh. Module set verified with expo-modules-autolinking resolve -p android. Part of the series.