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

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 BundleStep 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 BundleStep 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 BundleStep 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
initialNumToRenderandwindowSizein 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 listand update per Expo’s compatibility guide. - OTA Issues: Verify
EXPO_TOKENandapp.config.js’supdates.url. Runeas 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}, andremoveClippedSubviews={true}. - Install
react-native-fast-image(npm install react-native-fast-image) for image optimization. - Use
InteractionManagerfor 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 TouchConclusion
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
- dopebase.com - Mobile app development resources and templates
- instamobile.io - Production-ready React Native templates
- instaflutter.com - Flutter templates for cross-platform development
- reactnative.dev - Official React Native documentation
- docs.expo.dev - Expo SDK and EAS documentation
- Expo Snack - Quick prototyping tool
- Stack Overflow - Community solutions for React Native issues