
React Native Performance in 2025: A Field Guide
11 Sept 2025 · 8 min read
React NativePerformance
React Native Performance in 2025: A Field Guide
Practical tactics to make your React Native apps feel instant: faster startup, smoother rendering, crisp animations, and lean memory—without breaking developer velocity.
Who this is for
What you’ll improve
How to use this guide
Start with the Quick Checklist, then apply targeted fixes per section. Measure before/after—ship improvements weekly.
Quick checklist (ship these first)
global.HermesInternal
.
require()
/initialization behind import()
and lazy screens.
FlatList
with sane windowSize
, getItemLayout
, stable keys, and memoized rows.
1) Measure what matters
Tools: Flipper (React DevTools & Perf), Android Perfetto/Systrace, iOS Instruments (Time Profiler, Allocations).
2) Faster startup
import()
; initialize SDKs after first render via InteractionManager.runAfterInteractions
.
// Defer non-critical modules
useEffect(() => {
let mounted = true;
InteractionManager.runAfterInteractions(async () => {
if (!mounted) return;
const { initAnalytics } = await import("./analytics");
initAnalytics();
});
return () => { mounted = false; };
}, []);
3) Rendering: fewer, cheaper re-renders
React.memo
for stable, pure row/components.
useCallback
and derive props with useMemo
.
// Stable row with memo + keyExtractor
const Row = React.memo(({ item }) => {
return (<View className="flex-row items-center py-3">
<Text className="text-base">{item.title}</Text>
</View>);
});
<FlatList
data={data}
keyExtractor={(it) => it.id}
renderItem={({ item }) => <Row item={item} />}
/>
4) Lists: virtualization that feels native
Configure FlatList
for your content physiology—don’t ship defaults blindly.
<FlatList
data={feed}
keyExtractor={(it) => it.id}
renderItem={renderRow}
initialNumToRender={10}
windowSize={7}
maxToRenderPerBatch={10}
updateCellsBatchingPeriod={50}
removeClippedSubviews
getItemLayout={(data, index) => (
{ length: ROW_HEIGHT, offset: ROW_HEIGHT * index, index }
)}
/>
getItemLayout
for fixed-height rows.
removeClippedSubviews
on long feeds.
ScrollView
s; prefer one virtualized list.
5) Animations: keep them off the JS thread
Use a native/UISide execution path (e.g., Reanimated worklets) for buttery 60fps when JS is busy.
// Example with Reanimated (pseudo)
const progress = useSharedValue(0);
useEffect(() => { progress.value = withTiming(1, { duration: 400 }); }, []);
const style = useAnimatedStyle(() => ({
transform: [{ translateY: (1 - progress.value) * 20 }],
opacity: progress.value,
}));
return <Animated.View style={style} />;
- Prefer transform/opacity over expensive layout properties.
- Coalesce animations; avoid overlapping layout thrash.
6) Images: right size, right time
// Prefetch before showing a gallery
useEffect(() => {
Image.prefetch(url);
}, [url]);
7) Memory: avoid slow leaks
8) Bridge & JSI: fewer crossings, bigger batches
// Pseudo: debounce chatty updates before crossing the boundary
const batchedSend = debounce((payloads) => NativeModule.send(payloads), 16);
9) Release build hygiene
Android
minifyEnabled true
, shrinkResources true
; keep rules for RN/JSI libs.
android:hardwareAccelerated="true"
(default) for animation-heavy screens.
iOS
Common pitfalls
ScrollView
(no virtualization).
require()
chains at app start.
Do I need the New Architecture to be fast?
Not strictly—but Fabric/TurboModules can reduce overhead for rendering/native calls. Adopt where your dependencies are compatible; measure before/after.
Should I swap libraries for performance?
Only when profiling proves a bottleneck. Often configuration (lists, images, animations) unlocks most wins without churn.