Skip to main content

Improving React Native App Performance on Older Android Devices: Essential Tips

· 10 min read
Mobile Developer
Last updated on October 23, 2025

improving react native performance

This short guide helps you make React Native apps run smoother on older Android phones (API 21–28) by applying practical, beginner-friendly optimizations. Follow the steps to enable Hermes, shrink and harden your APK with ProGuard, optimize list and image rendering, and deploy fixes quickly with EAS OTA updates so users on low-end devices get a noticeably better experience.

Older Android devices (API levels 21–28, e.g., Android 5.0–8.1) often face performance challenges with React Native apps due to limited CPU, RAM, and GPU resources. This beginner-friendly guide shows you how to optimize React Native 0.75.4 apps with Expo SDK 51 for smooth performance on these devices, using Hermes configuration, ProGuard setup, and FlatList optimization. Expo SDK 51 supports the New Architecture, including bridgeless mode, for better efficiency. We’ll ensure compatibility with Android SDK 35 and iOS (Xcode 16+, iOS 18 SDK), using TypeScript for type safety and Over-The-Air (OTA) updates via EAS for deployment. For upgrading to newer React Native versions, see the React Native upgrade guide. For pre-optimized templates, check Instamobile or Dopebase.

Let’s make your app run smoothly on older Android hardware!


Prerequisites

Ensure you have:

  • Node.js (v20.x or later, LTS): nodejs.org
  • npm (v10.x or later)
  • EAS CLI: For Expo management
  • Android Studio (2024.3.2+): Android SDK 35, NDK r25+
  • Xcode (16+): iOS 18 SDK (macOS only)
  • VS Code or similar editor
  • Git: For version control
  • A React Native project (Expo SDK 51)
  • macOS: For iOS builds

Note: Expo Go supports SDK 51 but may have limitations with custom native code or New Architecture features. Use Expo Snack for quick prototyping.

What is performance optimization? It involves reducing app size, speeding up rendering, and lowering resource usage to ensure smooth operation on low-end devices like older Android phones. Learn more in React Native’s performance guide.


Step 1: Enable Hermes Engine

Hermes, a lightweight JavaScript engine, improves execution speed on older Android devices.

1.1 Enable in Expo

Update app.config.js to enable Hermes configuration:

export default () => ({
name: "MyApp",
slug: "myapp",
version: "1.0.0",
jsEngine: "hermes", // Enable Hermes
orientation: "portrait",
icon: ".https://docs.instamobile.io/assets/icon.png",
splash: {
image: ".https://docs.instamobile.io/assets/splash.png",
resizeMode: "contain",
backgroundColor: "#ffffff"
},
ios: {
bundleIdentifier: "com.myapp.app",
buildNumber: "1.0.0"
},
android: {
package: "com.myapp.app",
versionCode: 1
},
updates: {
enabled: true,
url: "https://u.expo.dev/your-project-id",
checkAutomatically: "ON_LOAD",
fallbackToCacheTimeout: 0
}
});

1.2 Verify Hermes

Run the app:

npx expo start

Check logs for "Hermes enabled." For bare workflows, update android/app/build.gradle:

project.ext.react = [
enableHermes: true
]

Then rebuild:

cd android && ./gradlew clean && ./gradlew assembleDebug

What is Hermes? Hermes compiles JavaScript to bytecode at build time, reducing app startup time and memory usage, critical for older Android devices. See Hermes documentation.


Step 2: Configure ProGuard for APK Optimization

ProGuard reduces APK size and optimizes code for low-end devices.

2.1 Enable ProGuard

Update android/app/build.gradle for ProGuard setup:

android {
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"
}
}
}

2.2 Add ProGuard Rules

Create android/app/proguard-rules.pro:

-keep class com.facebook.react.** { *; }
-dontwarn com.facebook.react.**
-keep class com.myapp.** { *; } # Replace with your app’s package

Build release APK:

cd android && ./gradlew assembleRelease

Test on an older Android emulator (API 24).

What is ProGuard? ProGuard shrinks and obfuscates code, reducing APK size by 20–30%, which improves download times and memory usage on older devices. See ProGuard documentation.


Mega Bundle Sale is ON! Get ALL of our React Native codebases at 90% OFF discount 🔥

Get the Mega Bundle

Step 3: Optimize List Rendering with FlatList

FlatList ensures smooth scrolling on resource-constrained devices.

3.1 Install Dependencies

Install Axios for data fetching:

npm install [email protected]

3.2 Set Up Optimized FlatList

Update src/App.tsx for FlatList optimization:

import { useEffect, useState } from 'react';
import { View, Text, FlatList } from 'react-native';
import axios from 'axios';

type Item = { id: number; title: string };

export default function App() {
const [items, setItems] = useState<Item[]>([]);
const [loading, setLoading] = useState(true);

useEffect(() => {
const fetchItems = async () => {
try {
const response = await axios.get('https://jsonplaceholder.typicode.com/posts');
setItems(response.data);
} catch (error) {
console.error('Error fetching items:', error);
} finally {
setLoading(false);
}
};
fetchItems();
}, []);

return (
<View style={{ flex: 1, padding: 20 }}>
{loading ? (
<Text>Loading...</Text>
) : (
<FlatList
data={items}
keyExtractor={item => item.id.toString()}
renderItem={({ item }) => <Text>{item.title}</Text>}
initialNumToRender={10}
maxToRenderPerBatch={5}
windowSize={3}
removeClippedSubviews={true}
getItemLayout={(data, index) => ({ length: 50, offset: 50 * index, index })} // Fixed height items
/>
)}
</View>
);
}

What is FlatList optimization? Props like initialNumToRender, maxToRenderPerBatch, and removeClippedSubviews limit rendered items, reducing CPU and memory load on older Android devices.


Step 4: Reduce Image and Asset Overhead

Large images strain older devices’ resources.

4.1 Install React Native Fast Image

Install:

npm install react-native-fast-image

4.2 Optimize Images in FlatList

Update src/App.tsx:

import { useEffect, useState } from 'react';
import { View, FlatList } from 'react-native';
import FastImage from 'react-native-fast-image';

type Item = { id: number; image: string };

export default function App() {
const [items, setItems] = useState<Item[]>([]);
const [loading, setLoading] = useState(true);

useEffect(() => {
const fetchItems = async () => {
try {
// Mock image data
setItems(Array.from({ length: 100 }, (_, i) => ({
id: i,
image: `https://via.placeholder.com/100?text=Item${i}`
})));
} catch (error) {
console.error('Error fetching items:', error);
} finally {
setLoading(false);
}
};
fetchItems();
}, []);

return (
<View style={{ flex: 1, padding: 20 }}>
{loading ? (
<Text>Loading...</Text>
) : (
<FlatList
data={items}
keyExtractor={item => item.id.toString()}
renderItem={({ item }) => (
<FastImage
style={{ width: 100, height: 100, marginVertical: 5 }}
source={{ uri: item.image, priority: FastImage.priority.normal }}
resizeMode={FastImage.resizeMode.cover}
/>
)}
initialNumToRender={10}
maxToRenderPerBatch={5}
windowSize={3}
removeClippedSubviews={true}
/>
)}
</View>
);
}

Compress images to <100KB and use WebP format for smaller sizes.

What is asset overhead? Unoptimized images increase load times and memory usage. react-native-fast-image caches and resizes images for faster rendering.


Mega Bundle Sale is ON! Get ALL of our React Native codebases at 90% OFF discount 🔥

Get the Mega Bundle

Step 5: Minimize JavaScript Thread Blocking

Prevent long JavaScript tasks from causing jank.

5.1 Use InteractionManager

Update src/App.tsx:

import { InteractionManager } from 'react-native';

useEffect(() => {
InteractionManager.runAfterInteractions(() => {
// Heavy computation, e.g., data processing
const processedData = items.map(item => ({ ...item, processed: true }));
setItems(processedData);
});
}, [items]);

5.2 Optimize Heavy Tasks

For complex logic, consider native modules or Hermes’ lightweight execution.

What is JavaScript thread blocking? Long-running JavaScript tasks block the UI thread, causing stuttering. InteractionManager schedules tasks after animations, improving responsiveness.


Step 6: Test on Older Devices

Verify performance on low-end Android devices.

6.1 Set Up Android Emulator

Create an Android Virtual Device (AVD) with API 24 (Android 7.0):

  • In Android Studio, go to AVD Manager > Create Virtual Device > Select low-end profile (e.g., Pixel 2, API 24).

Run:

npx expo run:android --device

6.2 Profile with Flipper

Install Flipper:

npm install react-native-flipper

Enable Performance Monitor in Flipper to track FPS and memory usage during scrolling.

What is FPS? Frames per second measures UI smoothness. Aim for 60 FPS, even on older devices.


Mega Bundle Sale is ON! Get ALL of our React Native codebases at 90% OFF discount 🔥

Get the Mega Bundle

Step 7: Deploy Optimizations with OTA Updates

Deploy performance fixes instantly with Over-The-Air (OTA) updates.

7.1 Configure OTA

Install expo-updates:

npm install expo-updates

Check compatibility at docs.expo.dev.

Ensure app.config.js includes OTA settings (Step 1.1).

Update src/App.tsx for OTA updates:

import { useEffect, useState } from 'react';
import { View, FlatList } from 'react-native';
import * as Updates from 'expo-updates';
import FastImage from 'react-native-fast-image';

type Item = { id: number; image: string };

export default function App() {
const [items, setItems] = useState<Item[]>([]);
const [loading, setLoading] = useState(true);

useEffect(() => {
const checkForUpdates = async () => {
try {
const update = await Updates.checkForUpdateAsync();
if (update.isAvailable) {
await Updates.fetchUpdateAsync();
await Updates.reloadAsync();
}
} catch (e) {
console.error('Update check failed:', e);
}
};
if (!__DEV__) {
checkForUpdates();
}

const fetchItems = async () => {
try {
setItems(Array.from({ length: 100 }, (_, i) => ({
id: i,
image: `https://via.placeholder.com/100?text=Item${i}`
})));
} catch (error) {
console.error('Error fetching items:', error);
} finally {
setLoading(false);
}
};
fetchItems();
}, []);

return (
<View style={{ flex: 1, padding: 20 }}>
{loading ? (
<Text>Loading...</Text>
) : (
<FlatList
data={items}
keyExtractor={item => item.id.toString()}
renderItem={({ item }) => (
<FastImage
style={{ width: 100, height: 100, marginVertical: 5 }}
source={{ uri: item.image, priority: FastImage.priority.normal }}
resizeMode={FastImage.resizeMode.cover}
/>
)}
initialNumToRender={10}
maxToRenderPerBatch={5}
windowSize={3}
removeClippedSubviews={true}
/>
)}
</View>
);
}

7.2 Publish Update

Configure EAS Update:

eas update:configure

Publish:

eas update --branch production

Test with a preview build:

eas build --profile preview --platform android

Troubleshooting Tips

  • Janky Scrolling: Adjust initialNumToRender and windowSize in FlatList optimization. Verify Hermes configuration.
  • High Memory Usage: Apply ProGuard setup and optimize images (Step 4).
  • Android Build Hangs: For React Native 0.75.4, increase Gradle memory in android/gradle.properties (org.gradle.jvmargs=-Xmx4096m) or clear caches (./gradlew clean). See Stack Overflow for solutions.
  • Dependency Issues: Run npm list and update per Expo’s compatibility guide.
  • OTA Issues: Verify EXPO_TOKEN and app.config.js’s updates.url. Run eas update:configure.
  • Expo Go Limitations: If using custom native code or New Architecture, build a custom dev client instead of Expo Go. Test with Expo Snack.

End-of-Article Recap

You’ve learned to:

  • Enable Hermes configuration for faster JavaScript execution.
  • Apply ProGuard setup to shrink APK size.
  • Optimize FlatList configuration for smooth scrolling.
  • Reduce image overhead with react-native-fast-image.
  • Minimize JavaScript thread blocking with InteractionManager.
  • Test on older Android devices and deploy via OTA updates.

Quick Reference Checklist

  • Enable Hermes in app.config.js (jsEngine: "hermes").
  • Configure ProGuard setup in android/app/build.gradle.
  • Optimize FlatList configuration with initialNumToRender={10}, maxToRenderPerBatch={5}, and removeClippedSubviews={true}.
  • Install react-native-fast-image (npm install react-native-fast-image) for image optimization.
  • Use InteractionManager for heavy tasks.
  • Test on API 24 emulator with Flipper’s Performance Monitor.
  • Deploy with EAS Update (eas update --branch production).

Looking for a custom mobile application?

Our team of expert mobile developers can help you build a custom mobile app that meets your specific needs.

Get in Touch

Conclusion

You’ve optimized your React Native 0.75.4 app with Expo SDK 51 for older Android devices, ensuring smooth performance with Hermes, ProGuard, and FlatList. For new projects, adopt Expo SDK 53 with the New Architecture to prepare for old architecture deprecations. For upgrading to newer React Native versions, see the React Native upgrade guide. For pre-optimized templates, explore Instamobile or Dopebase. Dive deeper with Expo’s documentation or join the community at reactnative.dev.

Additional Resources