Performance is the one non-functional requirement that users treat as functional. They do not file tickets saying “JS thread congestion”; they say “the app feels laggy” and uninstall. Hermes and JSI are not silver bullets, but they change the physics of your latency budget—especially on Android, where device variance is wider than most iOS-centric design reviews assume.
This guide is written for React Native developers who already know how to build features and now need a repeatable way to hunt regressions, justify refactors to leadership, and stop arguing about “feel” without numbers. Expect concrete habits: what to profile first, which metrics correlate with star ratings, and how to pair JS work with native instrumentation without drowning in noise.
Define a latency budget before you optimize
A latency budget is a contract with yourself: how many milliseconds navigation may take, how long a gesture may wait before visual feedback, and how much work may occur on the JS thread during a scroll burst. Without numbers, every optimization is vibes. With numbers, you can say “we regressed cold start by 120ms in build 412; revert or accept.”
Start with three measurements everyone agrees matter: cold start to interactive home, time-to-first-keystroke on your slowest form screen, and scroll FPS on your longest list with real data—not placeholder items. Capture them on a reference low-end Android and a mid-tier iPhone. Store results in your repo wiki or Notion with device model, OS version, and commit hash.
Business translation: shaving 200ms off checkout completion often beats adding a trendy animation. Leadership understands revenue risk more easily than frame timelines; learn to connect them without exaggeration.
Hermes: what it improves, and what it cannot fix
Startup wins vs. render-path debt
Hermes optimizes for mobile startup and memory characteristics that matter on constrained devices. Smaller bytecode footprint and ahead-of-time compilation mean less time parsing huge bundles before your first screen renders. That directly attacks one of the loudest complaints in RN reviews: slow first open after install.
What Hermes does not do is forgive expensive render paths. If your screen re-renders fifty child components because context updates fan out too aggressively, Hermes will execute that churn efficiently—and users will still see jank. The fix is React discipline: memoization where it pays, stable props, splitting contexts, and moving animation off the JS thread with Reanimated where appropriate.
Real-world anecdote: a retail client’s product grid stuttered despite Hermes. Profiling showed image decoding spikes on the UI thread from oversized remote thumbnails. Switching to progressive thumbnails and fixed dimensions removed more jank than any JS micro-optimization. The lesson: always ask if the bottleneck is even JavaScript.
JSI and native interop: measure serialization, not vibes
When libraries adopt JSI, many cross-language calls become cheaper. The engineering takeaway is not “JSI fast good,” but “fewer copies and fewer surprises about what runs where.” When integrating a new native module, profile both threads during a representative call burst. Watch for accidental marshaling of large blobs—base64 images, huge JSON payloads—across the boundary.
Instrument with markers around native calls from JS and correlate with Android Systrace or iOS Instruments time profiles. If you see JS thread blocks lining up with native work that should be async, you have a design bug, not a tuning knob.
Lists: where React Native apps live or die
Virtualization, images, and “felt” smoothness
Most user hours accumulate in feeds, chat, and catalogs. Use FlashList or carefully tuned FlatList configurations: stable keys, window sizes matched to item complexity, getItemLayout when heights are deterministic, and avoid inline anonymous functions that defeat memoization. Measure with empty states, partial loads, and “pull to refresh” because those transitions expose layout thrash.
Do not forget images. Caching strategy, priority hints, and placeholder sizing prevent cumulative layout shift that feels like “lag” even when FPS counters look fine. Pair with CDN transforms so you never decode a 4000px image to display a 96px avatar.
InteractionManager, idle work, and the “after transition” trap
Heavy work scheduled right after navigation competes with the very frames you want to feel crisp. InteractionManager.runAfterInteractions is an old API but still a useful mental model: defer non-critical prefetch, log uploads, and analytics enrichment until animations settle. Combine with requestIdleCallback patterns on supported paths or simple queued microtasks in JS—just never block the first paint waiting for optional telemetry.
A pattern that works in production: split “must have for screen usable” from “nice once stable.” Prefetch the next route’s JSON only after the current screen’s list has rendered its first window. Users perceive intelligence when the second screen appears instant; they perceive bugs when the first screen stutters because you were too eager.
Memory, ANRs, and Android’s unforgiving main thread
Android Application Not Responding traces are blunt instruments: they punish anything that starves the main thread, including image decoding, synchronous storage reads, or a native module doing too much during resume. Watch memory as a leading indicator—leaky image caches and retained timers show up as growing heap long before crashes spike. React Native’s dev menu perf overlay is a starting point; Android Studio’s Memory Profiler and Xcode’s Leaks instrument are where you confirm suspicions.
On low-RAM devices, aggressive backgrounding policies mean your process dies more often. Cold start performance and state restoration paths become part of perceived speed. Test “user switches away to camera and returns” not only happy-path taps inside a single session.
Networking: parallelize wisely, cancel aggressively
Waterfalls of sequential fetches inflate time-to-content even when each individual request is “fast.” Adopt query libraries that dedupe in-flight calls, cancel when screens unmount, and surface stale-while-revalidate UX so lists populate progressively. Pair with HTTP caching headers your backend actually honors; mobile networks punish chatty APIs harder than office Wi-Fi ever will.
When GraphQL or BFF endpoints return large nested trees, measure payload bytes on cellular. Sometimes splitting one mega-query into two parallel requests reduces JS parse time enough to improve interactivity even if total bytes are similar—because smaller JSON objects deserialize faster and fail more locally.
Profiling cadence that teams will actually follow
Big-bang profiling marathons decay. Instead, attach a fifteen-minute profiling checklist to releases: capture startup trace, scroll the heaviest list for thirty seconds with CPU overlay, background and restore the app twice. Save artifacts in CI or a shared drive. When regressions appear, you can diff against the last known-good trace instead of guessing which dependency shifted.
Automate what is cheap: bundle size budgets, Hermes on/off comparisons for critical paths, and basic performance tests on simulators for smoke only—then validate on hardware before marketing promises ship. Tag releases in your analytics so you can correlate crash-free users and session length with the exact build customers experienced—not the build you wish they had installed.
Key takeaways
- Define numeric latency budgets for start, input, and scroll before tuning.
- Hermes helps startup and memory; expensive React trees still need architectural fixes.
- Profile native bridges for payload size and threading, especially with new SDKs.
- Invest in list and image strategies—they dominate perceived performance.
- Short, repeatable profiling rituals beat rare, heroic optimization sprints.
If your React Native app needs a performance pass, release hardening, or a second opinion on architecture, contact me through my portfolio. I work with teams that want senior-level diagnosis—not generic “optimize React” advice detached from your actual users, devices, and analytics.
Senior Mobile Engineer building AI-native React Native products. Available for freelance contracts.
