A practical checklist for faster React Native apps: measuring correctly, startup time, lists, re-renders, animations, images, network and release builds.
In this article
Measure before you optimize
Most React Native performance work is wasted because it targets the wrong problem. Always measure on a release build running on a real mid-range Android phone, not in development mode on a simulator. Development builds run extra checks and are much slower, so they exaggerate some problems and hide others.
Use the React DevTools profiler, available through React Native DevTools, to see which components re-render and why. Use Android Studio's profiler and Xcode Instruments for CPU, memory and frame timing at the native level. In production, a monitoring tool such as Sentry or Firebase Performance Monitoring shows startup times and slow screens across real devices.
Know which thread is struggling
A React Native app has a JavaScript thread that runs your React code and a UI thread that draws native views. If the JavaScript thread is busy, taps and data updates feel delayed. If the UI thread is busy, scrolling and animations drop frames. The built-in performance monitor in the developer menu shows frame rates for both, which tells you where to look first.
Current React Native versions run only on the New Architecture (Fabric rendering and TurboModules over JSI), which became the sole architecture in React Native 0.82, with the Hermes JavaScript engine as the default. If you maintain an older app still on the legacy architecture, you must migrate before you can upgrade, and the move is often the single biggest performance improvement available, though it requires checking that your native libraries are compatible.
Startup time checklist
Users judge an app in its first seconds. Work through these items:
- Confirm Hermes is enabled, since it precompiles JavaScript to bytecode
- Lazy-load screens that are not needed at launch instead of importing everything in the root
- Defer analytics, remote config and other SDK initialization until after the first screen renders
- Avoid large synchronous work, such as parsing big JSON files, at startup
- Audit dependencies and remove heavy libraries used for one small feature
Lists checklist
Long lists are the most common source of jank. Never render long data with a ScrollView and map; use FlatList, SectionList or Shopify's FlashList, which recycles views and is often faster for large lists.
- Provide a stable keyExtractor based on item IDs, never array indexes for changing data
- Use getItemLayout when rows have a fixed height, so the list can skip measurement
- Tune initialNumToRender and windowSize to balance memory and blank areas while scrolling
- Wrap row components in React.memo and define renderItem outside the render body or with useCallback
- Keep row components light: no heavy calculations or nested lists inside rows
Re-render checklist
Unnecessary re-renders cost JavaScript thread time. The profiler shows which components render and what triggered them. Fix the biggest offenders first:
- Split large components so a state change only re-renders the part that uses it
- Avoid putting fast-changing values in a React context shared by many components
- Use selectors with state libraries so components subscribe to only the fields they need
- Use memo, useMemo and useCallback where profiling shows a benefit, not everywhere by default
- Avoid creating new objects and arrays as props on every render for memoized children
Animations and gestures
Animations that run on the JavaScript thread stutter whenever that thread is busy. With the built-in Animated API, set useNativeDriver to true for supported properties such as opacity and transform. For complex or gesture-driven animations, use react-native-reanimated, whose worklets run on the UI thread, together with react-native-gesture-handler. Animate transform and opacity rather than layout properties like width and height where possible, since layout changes are more expensive.
Images, network and memory
Images are often the largest cost in both memory and bandwidth. Serve images resized for the device from your backend or an image CDN instead of downloading full-size photos, prefer WebP, and use an image component with disk caching, such as expo-image. Always give images explicit dimensions to avoid layout shifts.
For data, paginate API calls, cache responses with a library such as TanStack Query and avoid request waterfalls where one screen waits for several sequential calls. Clean up listeners, timers and subscriptions in effect cleanups to prevent memory leaks, which show up as apps slowing down the longer they run.
Release build checklist
A few build settings make a measurable difference before every release:
- Remove console.log calls from production bundles, for example with a Babel plugin
- Enable code shrinking with R8 on Android and ship Android App Bundles
- Check bundle size after adding dependencies
- Test on a low-end Android device before each release, not just on flagship phones
- Upload source maps to your crash reporting tool so production stack traces stay readable
Navigation and screen transitions
Use a native stack navigator, such as the native stack in React Navigation, which relies on the platform's own navigation primitives for smoother transitions. Keep heavy work out of the transition itself: render a light placeholder first and load heavy content once the animation finishes, for example with InteractionManager.runAfterInteractions. In apps with many tabs or deep stacks, react-native-screens can freeze inactive screens so they stop re-rendering in the background.
Storage and native work
AsyncStorage is fine for small, infrequent values but slow for large or hot data. For values read constantly, such as settings or tokens, a JSI-based key-value store like react-native-mmkv is much faster, and for larger structured data SQLite is a better fit than serializing big JSON blobs. Move heavy computation, such as image processing or large parsing jobs, into native modules or background tasks rather than the JavaScript thread. Even with JSI, converting very large objects between JavaScript and native code has a cost, so pass only what is needed.
Keep performance from regressing
Performance is not a one-time project. Set simple budgets, such as a startup time target on a reference device, track them in monitoring and review them during releases. Our mobile app testing and performance testing services include device-based profiling. For framework background, see our React Native explainer and Nexzem's React Native app development page.
Useful budgets include time to the first usable screen, frame drops while scrolling the main list and memory use after ten minutes of normal use. Measure them on the same reference device for every release, so a regression shows up as a change in the trend rather than a vague complaint from users.


