Expert in React Native performance optimization including bundle size reduction, memory management, rendering optimization, image optimization, list performance, navigation optimization, startup time, FlatList, memoization, React.memo, useMemo, useCallback, lazy loading, code splitting. Activates for performance, slow app, lag, memory leak, bundle size, optimization, flatlist performance, re-render, fps, jank, startup time, app size.
Inherits all available tools
Additional assets for this skill
This skill inherits all available tools. When active, it can use any tool Claude has access to.
Specialized in optimizing React Native and Expo applications for production. Expert in reducing bundle size, improving render performance, optimizing memory usage, and eliminating jank.
Analyzing Bundle Size
# Generate bundle stats (Expo)
npx expo export --dump-sourcemap
# Analyze with source-map-explorer
npx source-map-explorer bundles/**/*.map
# Check production bundle size
npx expo export --platform ios
du -sh dist/
# Metro bundle visualizer
npx react-native-bundle-visualizer
Reducing Bundle Size
Hermes Configuration
// app.json (Expo)
{
"expo": {
"jsEngine": "hermes", // Faster startup, smaller bundle
"ios": {
"jsEngine": "hermes"
},
"android": {
"jsEngine": "hermes"
}
}
}
React.memo for Component Optimization
import React, { memo } from 'react';
// Without memo: Re-renders on every parent render
const UserCard = ({ user }) => (
<View>
<Text>{user.name}</Text>
</View>
);
// With memo: Only re-renders when user prop changes
const UserCard = memo(({ user }) => (
<View>
<Text>{user.name}</Text>
</View>
));
// Custom comparison function
const UserCard = memo(
({ user }) => <View><Text>{user.name}</Text></View>,
(prevProps, nextProps) => prevProps.user.id === nextProps.user.id
);
useMemo and useCallback
import { useMemo, useCallback } from 'react';
function UserList({ users, onUserPress }) {
// Expensive calculation - only recalculates when users changes
const sortedUsers = useMemo(() => {
console.log('Sorting users...');
return users.sort((a, b) => a.name.localeCompare(b.name));
}, [users]);
// Stable callback reference - prevents child re-renders
const handlePress = useCallback((userId) => {
console.log('User pressed:', userId);
onUserPress(userId);
}, [onUserPress]);
return (
<FlatList
data={sortedUsers}
renderItem={({ item }) => (
<UserItem user={item} onPress={handlePress} />
)}
keyExtractor={item => item.id}
/>
);
}
Avoiding Inline Functions and Objects
// ❌ BAD: Creates new function on every render
<TouchableOpacity onPress={() => handlePress(item.id)}>
<Text style={{ color: 'blue' }}>Press</Text>
</TouchableOpacity>
// ✅ GOOD: Stable references
const styles = StyleSheet.create({
buttonText: { color: 'blue' }
});
const handleItemPress = useCallback(() => {
handlePress(item.id);
}, [item.id]);
<TouchableOpacity onPress={handleItemPress}>
<Text style={styles.buttonText}>Press</Text>
</TouchableOpacity>
Optimized FlatList Configuration
import { FlatList } from 'react-native';
function OptimizedList({ data }) {
const renderItem = useCallback(({ item }) => (
<UserCard user={item} />
), []);
const keyExtractor = useCallback((item) => item.id, []);
return (
<FlatList
data={data}
renderItem={renderItem}
keyExtractor={keyExtractor}
// Performance optimizations
initialNumToRender={10} // Render 10 items initially
maxToRenderPerBatch={10} // Render 10 items per batch
windowSize={5} // Keep 5 screens worth of items
removeClippedSubviews={true} // Unmount off-screen items
updateCellsBatchingPeriod={50} // Batch updates every 50ms
// Memoization
getItemLayout={getItemLayout} // For fixed-height items
// Optional: Performance monitor
onEndReachedThreshold={0.5} // Load more at 50% scroll
onEndReached={loadMoreData}
/>
);
}
// For fixed-height items (huge performance boost)
const ITEM_HEIGHT = 80;
const getItemLayout = (data, index) => ({
length: ITEM_HEIGHT,
offset: ITEM_HEIGHT * index,
index,
});
FlashList (Better than FlatList)
// Install: npm install @shopify/flash-list
import { FlashList } from "@shopify/flash-list";
function SuperFastList({ data }) {
return (
<FlashList
data={data}
renderItem={({ item }) => <UserCard user={item} />}
estimatedItemSize={80} // Required: approximate item height
/>
);
}
Fast Image for Better Performance
// Install: npm install react-native-fast-image
import FastImage from 'react-native-fast-image';
function ProfilePicture({ uri }) {
return (
<FastImage
style={{ width: 100, height: 100 }}
source={{
uri: uri,
priority: FastImage.priority.normal, // high, normal, low
cache: FastImage.cacheControl.immutable // Aggressive caching
}}
resizeMode={FastImage.resizeMode.cover}
/>
);
}
Image Optimization Best Practices
// Use appropriate sizes (not 4K images for thumbnails)
<Image
source={{ uri: 'https://example.com/image.jpg?w=200&h=200' }}
style={{ width: 100, height: 100 }}
/>
// Use local images when possible (bundled)
<Image source={require('./assets/logo.png')} />
// Progressive loading
import { Image } from 'react-native';
<Image
source={{ uri: imageUrl }}
defaultSource={require('./placeholder.png')} // iOS only
style={{ width: 200, height: 200 }}
/>
Preventing Memory Leaks
import { useEffect } from 'react';
function Component() {
useEffect(() => {
// Set up subscription
const subscription = api.subscribe(data => {
console.log(data);
});
// Clean up on unmount (CRITICAL!)
return () => {
subscription.unsubscribe();
};
}, []);
// Timers
useEffect(() => {
const timer = setInterval(() => {
console.log('Tick');
}, 1000);
return () => clearInterval(timer); // Clean up timer
}, []);
}
Image Memory Management
// Clear image cache when memory warning
import { Platform, Image } from 'react-native';
import FastImage from 'react-native-fast-image';
if (Platform.OS === 'ios') {
// iOS: Clear cache on memory warning
DeviceEventEmitter.addListener('RCTMemoryWarning', () => {
FastImage.clearMemoryCache();
});
}
// Manual cache clearing
FastImage.clearMemoryCache();
FastImage.clearDiskCache();
Lazy Loading Screens
import { lazy, Suspense } from 'react';
import { ActivityIndicator } from 'react-native';
// Lazy load heavy screens
const ProfileScreen = lazy(() => import('./screens/ProfileScreen'));
const SettingsScreen = lazy(() => import('./screens/SettingsScreen'));
function App() {
return (
<Suspense fallback={<ActivityIndicator />}>
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name="Profile" component={ProfileScreen} />
<Stack.Screen name="Settings" component={SettingsScreen} />
</Stack.Navigator>
</NavigationContainer>
</Suspense>
);
}
React Navigation Optimization
// Freeze inactive screens (React Navigation v6+)
import { enableScreens } from 'react-native-screens';
enableScreens();
// Detach inactive screens
<Stack.Navigator
screenOptions={{
detachPreviousScreen: true, // Unmount inactive screens
}}
>
<Stack.Screen name="Home" component={HomeScreen} />
</Stack.Navigator>
Reducing Initial Load Time
// app.json - Optimize splash screen
{
"expo": {
"splash": {
"image": "./assets/splash.png",
"resizeMode": "contain",
"backgroundColor": "#ffffff"
}
}
}
// Use Hermes for faster startup
{
"expo": {
"jsEngine": "hermes"
}
}
Defer Non-Critical Initialization
import { InteractionManager } from 'react-native';
function App() {
useEffect(() => {
// Critical initialization
initializeAuth();
// Defer non-critical tasks until after animations
InteractionManager.runAfterInteractions(() => {
initializeAnalytics();
initializeCrashReporting();
preloadImages();
});
}, []);
return <AppContent />;
}
Use Native Driver
import { Animated } from 'react-native';
function FadeInView({ children }) {
const opacity = useRef(new Animated.Value(0)).current;
useEffect(() => {
Animated.timing(opacity, {
toValue: 1,
duration: 300,
useNativeDriver: true, // Runs on native thread (60fps)
}).start();
}, []);
return (
<Animated.View style={{ opacity }}>
{children}
</Animated.View>
);
}
Reanimated for Complex Animations
// Install: npm install react-native-reanimated
import Animated, { useSharedValue, useAnimatedStyle, withSpring } from 'react-native-reanimated';
function DraggableBox() {
const offset = useSharedValue(0);
const animatedStyle = useAnimatedStyle(() => ({
transform: [{ translateX: offset.value }],
}));
const handlePress = () => {
offset.value = withSpring(offset.value + 50);
};
return (
<Animated.View style={[styles.box, animatedStyle]}>
<Text>Drag me</Text>
</Animated.View>
);
}
Ask me when you need help with:
// In app, shake device → Show Perf Monitor
// Shows:
// - JS frame rate
// - UI frame rate
// - RAM usage
// Install: npm install @react-native-firebase/perf
import perf from '@react-native-firebase/perf';
// Custom trace
const trace = await perf().startTrace('user_profile_load');
await loadUserProfile();
await trace.stop();
// HTTP monitoring (automatic with Firebase)
import '@react-native-firebase/perf/lib/modular/index';
import { Profiler } from 'react';
function onRender(id, phase, actualDuration) {
if (actualDuration > 16) { // Slower than 60fps
console.warn(`Slow render in ${id}: ${actualDuration}ms`);
}
}
<Profiler id="UserList" onRender={onRender}>
<UserList users={users} />
</Profiler>
import { debounce } from 'lodash';
import { useCallback } from 'react';
function SearchScreen() {
const debouncedSearch = useCallback(
debounce((query) => {
performSearch(query);
}, 300),
[]
);
return (
<TextInput
onChangeText={debouncedSearch}
placeholder="Search..."
/>
);
}
Use FlashList or RecyclerListView instead of ScrollView with many items:
// ❌ BAD: Renders all 1000 items
<ScrollView>
{items.map(item => <ItemCard key={item.id} item={item} />)}
</ScrollView>
// ✅ GOOD: Only renders visible items
<FlashList
data={items}
renderItem={({ item }) => <ItemCard item={item} />}
estimatedItemSize={100}
/>
// ❌ BAD: Creates new style object on every render
<View style={{ backgroundColor: 'red', padding: 10 }} />
// ✅ GOOD: Reuses style object
const styles = StyleSheet.create({
container: {
backgroundColor: 'red',
padding: 10
}
});
<View style={styles.container} />
Performance Requirements
spec.md (e.g., <2s startup)tasks.md test plansPerformance Metrics
Living Documentation