Our Take
June 23, 2025

Scaling React Native for Performance: Lessons From a Real-World App

React Native moves fast when your app is small. But once you start layering in features, media uploads, and scroll-heavy views, you quickly realize how easy it is to slow everything down.

Here are real lessons from scaling a React Native app that needed to stay smooth, even on mid-range devices. No fluff, just what worked.

1. FlatList Is Non-Negotiable

If you're rendering a list with more than 10 items and using ScrollView, you're asking React Native to draw the entire thing at once. It's like trying to cook every meal of the week on Sunday night in one giant pot.

Use FlatList. It's built for recycling items off-screen and reducing memory usage.

<FlatList

 data={items}

 keyExtractor={(item) =>item.id.toString()}

 renderItem={({ item }) => <ListItemitem={item} />}

 initialNumToRender={10}

 windowSize={5}

/>

Set initialNumToRender and windowSize based on your screen size and content. The defaults are okay, but tuning them helps when you know what your users are doing.

2. Memoization Fixes Re-Renders That Kill Performance

If you don’t memoize list items, they’ll re-render every time a parent component updates. That’s like repainting every wall in your house just because you moved a chair.

tsx

CopyEdit

const ListItem =React.memo(({ item }) => {

 return <Text>{item.name}</Text>;

});

Use React.memo to wrap components that don’t need to update every time. Pair it with useCallback and useMemo to stabilize functions and objects passed down as props.

3. Lazy Load Screens and Expensive Components

We used to preload everything on app startup. Map views, videos, large modals. Our cold start time was awful.

Now, we only render heavy components when they’re needed.

{showVideo && <VideoPlayer />}

Or conditionally load a screen:

const LazyComponent= React.lazy(() => import('./HeavyScreen'));

It’s like not turning on all the lights in your house until someone actually walks into the room.

4. Use InteractionManager to Defer Work

React Native is single-threaded, and every JS task blocks the UI. If you’re doing something like logging analytics, parsing data, or fetching suggestions, wait until interactions are done.

useEffect(() => {

 const task =InteractionManager.runAfterInteractions(() => {

   // This happens after navigation finishes

 });

 return () => task.cancel();

}, []);

Keep animations and transitions snappy by getting out of the way.

5. Optimize Images Like Your App Depends On It

Because it does. Uncompressed images destroy memory, kill FPS, and cause layout jumps.

We used react-native-fast-image for proper caching and priority-based loading.

import FastImagefrom 'react-native-fast-image';

<FastImage

 style={{ width: 100, height: 100 }}

 source={{ uri: imageUrl, priority:FastImage.priority.high }}

 resizeMode={FastImage.resizeMode.cover}

/>

Also, compress images and videos before upload. We built native compression logic because off-the-shelf libraries weren’t reliable across both platforms.

6. Reanimated Makes Gestures and Animations Feel Native

The built-in Animated API is fine until you need fluid gestures or complex animations. Then it starts choking.

react-native-reanimated runs on the UI thread, not JS, so it doesn't get blocked by renders or timers.

import Animated, {

 useSharedValue,

 useAnimatedStyle,

 withSpring,

} from 'react-native-reanimated';

const offset =useSharedValue(0);

const animatedStyle= useAnimatedStyle(() => ({

 transform: [{ translateY: offset.value }],

}));

<Animated.Viewstyle={animatedStyle} />

It takes more setup, but it feels native because it is.

7. Flatten Your Views

Every extra View adds layout calculation. Nesting 12 levels deep for styling reasons adds up fast.

Before:

<View>

 <View>

   <View>

     <Text>Hello</Text>

   </View>

 </View>

</View>

After:

<View>

 <Text>Hello</Text>

</View>

Use DevTools or Xcode’s Debug View Hierarchy to spot over-nesting.

8. Keep Your JS Bundle Clean

Your users download everything in your bundle. If you’re importing massive libraries you don’t use or leaving dead code in features you scrapped, you're bloating the app for no reason.

To check bundle size:

npx react-nativebundle \

 --platform ios \

 --entry-file index.js \

 --bundle-output ./main.jsbundle \

 --sourcemap-output ./main.jsbundle.map

npx source-map-explorer main.jsbundle main.jsbundle.map

We found an old calendar lib that was adding 800kb to our bundle. Deleted it. No regrets.

React Native doesn’t have a performance problem by default. Most performance issues come from letting things pile up without thinking about how it all runs under the hood.

Use FlatList, memoize your components, lazy load your screens, compress your media, and don’t be afraid to rip out stuff that’s not pulling its weight. It’s not about making it perfect. It’s about making smart tradeoffs that let your app stay fast as it grows.

If you’re building in React Native and need help scaling performance or architecture, feel free to reach out. I’ve been there, and happy to share more than just the blog version.

- Written by Karly Lamm

Looking for Midwest-Based Developers for Your Business?

Open up new opportunities, grow revenue and improve user experience with Aviron Software. Based in St. Louis with USA programmers & QA specialists, Aviron can help your business whether on web, mobile apps or desktop software development. Get in touch via hello@avironsoftware.com or contact us.

Do you prefer to text? Send a text over to (314) 541-3446 to setup a time to discuss growing your business through software.