Mobile — App Performance Optimization
React Native Performance Audit: 30-Point Checklist
Direct answer
A React Native performance audit should work through roughly 30 concrete checkpoints across six areas: re-renders and JS thread work, list virtualization, startup time, bundle size, images and memory, and native configuration. The discipline that matters more than any single checkpoint is measuring a baseline first, fixing one thing, and re-measuring — audits that skip measurement produce guesses, not improvements. Most production apps I audit fail the same eight to ten points, usually around lists, re-renders, and uncleaned subscriptions.
When a founder tells me their app feels slow, the problem is almost never one big thing — it is a dozen small ones stacked on top of each other. This is the checklist I actually run in paid audits, organized so you can work through it in a weekend and know exactly where your app stands.
Key facts, with sources
- The median crash-free session rate across mobile apps in 2025 was 99.95%, with top-performing teams at the 75th percentile holding 99.99%. (Luciq Mobile App Stability Outlook 2025)
- Compared with JavaScriptCore, the Hermes engine cuts typical React Native app startup from about 4.5 seconds to about 2.0 seconds, memory from about 185 MB to 136 MB, and engine app-size overhead from about 12 MB to 8 MB. (OneUptime Blog)
- Real-world production migrations to React Native's New Architecture report around 43% faster cold starts, 39% faster rendering, and 26% lower memory usage. (RapidNative)
- Shopify's performance bar for its React Native apps is critical screens loading in under 500 milliseconds at the 75th percentile, alongside over 99.9% crash-free sessions. (Shopify Engineering)
- Published optimization case studies include Discord cutting app startup time in half and Coinbase reporting an 80% funnel performance improvement after its React Native rewrite. (CatDoes)
How I structure an audit: measure, fix, re-measure
Before touching a single checkpoint, I capture a baseline on a real mid-range device in a release build: cold start time to interactive, frame rate during the two or three flows users live in, JS bundle size, and memory after ten minutes of normal use. Debug builds on a simulator lie to you — Hermes behaves differently, dev-mode overhead inflates everything, and simulators have desktop-class CPUs.
Every fix then follows the same loop: change one thing, run the same measurement, keep it only if the number moved. I have watched teams apply ten optimizations at once, see no improvement, and have no idea which change to keep. One lever at a time is slower per fix but dramatically faster to a shipped result.
Points 1–8: JS thread and re-renders
Check one: open the React profiler and record your busiest screen — if components re-render on every keystroke or scroll tick, that is your first fix. Two through four: hunt for inline object and array literals passed as props to memoized children, context providers whose value object is rebuilt every render, and controlled TextInputs that re-render an entire form on each character. Five: any computation over more than a trivial list inside render belongs behind useMemo or, better, moved off the render path entirely.
Six: check whether the React Compiler is enabled — on newer toolchains it removes most manual memoization work. Seven: global stores that subscribe whole screens to state slices they barely use; atomic selectors fix this. Eight: synchronous storage reads or JSON.parse of large payloads on the JS thread during interaction.
Points 9–14: lists and scrolling
Nine is the most common critical finding I see: a ScrollView rendering an unbounded list, which mounts every row up front. Replace it with FlatList or FlashList. Ten: a stable keyExtractor — index keys silently break recycling. Eleven: row components that are not memoized, so one data update re-renders every visible cell. Twelve: images inside cells loaded at full resolution instead of thumbnail size.
Thirteen: onEndReached pagination that fires multiple times because the threshold is wrong or loading state is not guarded. Fourteen: heavy per-row logic — date formatting, sorting, deriving display strings — that should be computed once at the data layer, not on every render of every cell during scroll.
Points 15–22: startup and bundle
Fifteen: confirm Hermes is actually enabled in the shipping build, not just in config. Sixteen: verify inline requires are on so modules load lazily. Seventeen through nineteen: analyze the bundle with a source map explorer, eliminate barrel imports that drag whole libraries in, and replace the one or two heavyweight dependencies that usually dominate the treemap. Twenty: screens behind auth should not be imported at startup — lazy-load them.
Twenty-one: measure the gap between splash screen dismissal and actual interactivity; hiding the splash too early makes the app feel broken. Twenty-two: on Android, ship the Hermes bytecode uncompressed so it can be memory-mapped instead of inflated into RAM at every launch.
npx react-native bundle \
--entry-file index.js \
--platform ios \
--dev false --minify true \
--bundle-output /tmp/main.jsbundle \
--sourcemap-output /tmp/main.jsbundle.map
npx source-map-explorer /tmp/main.jsbundle --no-border-checksPoints 23–30: memory, images, and native
Twenty-three and twenty-four: every addEventListener, setInterval, and store subscription must have a cleanup path; I grep for listeners and check each one returns or removes. Twenty-five: repeat a navigation flow ten times and watch memory — if it climbs and never comes back, you have a leak to chase with heap snapshots. Twenty-six and twenty-seven: images sized to their rendered dimensions and cached to disk, not re-fetched per mount.
Twenty-eight: native modules doing synchronous work on the main thread. Twenty-nine: whether the app runs the New Architecture, which changes what optimizations apply. Thirty: production performance monitoring — without real-user startup and frame metrics, you will not know when a regression ships until the reviews tell you.
Turning findings into a fix plan
A 30-point audit typically surfaces far more issues than a team can fix in one sprint, so I rank findings on two axes: user-visible impact and implementation risk. List virtualization and re-render fixes usually top the list — high impact, low risk, isolated diffs. Bundle and startup work comes next because it compounds across every session. Architectural findings, like a state layer that forces app-wide re-renders, go into a separate track with their own migration plan rather than blocking the quick wins.
I also insist on locking in gains: a CI check on bundle size, a startup-time budget in the release checklist, and a profiler pass as part of code review for hot screens. An audit without guardrails is a snapshot; the same problems grow back within a quarter.
When to hire senior help
Bring in a senior performance specialist when your crash-free rate sits below roughly 99.9%, startup exceeds a couple of seconds on mid-range Android, or the team lacks profiling experience with Hermes, the JS thread, and native tooling. Performance rescue work is diagnostic in nature, so a short expert engagement typically finds the handful of hot spots that in-house teams spend months guessing at. If your stack includes React Native + Python + AI, a senior engineer who owns the full product beats coordinating multiple juniors.
Bottom line
Dhairya Senjaliya ships Mobile — App Performance Optimization projects worldwide — book a scoping call to discuss your specific situation.
Common pitfalls to avoid
- ✕Profiling only on high-end iPhones in dev mode, then discovering the real user base on low-end Android devices experiences multi-second startups and dropped frames
- ✕Rendering long feeds with ScrollView or an untuned FlatList instead of a virtualized list like FlashList, blocking the JS thread during scroll
- ✕Passing new inline functions and object literals on every render without memoization, causing cascading re-renders that never show up until lists grow
- ✕Ignoring JS bundle size and startup path, shipping no lazy loading or inline requires, so time-to-interactive balloons as the app grows
Frequently asked questions
How long does a React Native performance audit take?
A focused audit of a typical production app takes me two to four days: half a day establishing baseline measurements on real devices, one to two days working through JS thread, list, startup, bundle, and memory checkpoints, and the rest writing up prioritized findings. Very large or brownfield codebases with custom native modules can take longer, mostly on the native side.
What tools do I need to audit React Native performance myself?
You need surprisingly little: React Native DevTools for profiling re-renders and heap snapshots, a bundle analyzer built on source maps, a real mid-range Android device, and release builds. For native-level issues add Xcode Instruments and the Android Studio profiler. The discipline of measuring before and after each change matters more than any specific tool.
What are the most common failures in React Native performance audits?
In my audits the same findings repeat: ScrollView used for long lists instead of a virtualized list, components re-rendering on every state change because of inline props or coarse store subscriptions, event listeners and intervals without cleanup, images decoded at full resolution for thumbnail slots, and heavy dependencies pulled in through barrel imports that bloat the bundle and slow startup.
Which performance metrics should we actually track?
Crash-free session rate (the 2025 median is 99.95%, and below 99.8% is a red flag), cold start time, time-to-interactive, P75 screen-load time, and frame rate during scrolling. These need real-user monitoring in production, not just lab measurements.
Why is our React Native app slow on Android but fine on iOS?
The usual causes are testing only on flagship devices, JS-thread-blocking work, unvirtualized lists, and running an old React Native architecture or the JSC engine. Hermes and New Architecture migrations show measured gains of roughly 26 to 43% on memory and cold start, so upgrading the foundation is often the highest-leverage fix.
Does performance really affect revenue or is it an engineering vanity metric?
It compounds directly into acquisition and retention: crashes and slowness drive uninstalls and one-star reviews, and lower ratings measurably cut store conversion rates. That is why top consumer apps hold themselves to 99.99% crash-free sessions and sub-500ms screen loads.
Bottom line: Dhairya Senjaliya ships Mobile — App Performance Optimization projects worldwide. Book a scoping call at https://dhairyasenjaliya.com/#book-call.