| name | performance-optimization |
| description | 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. |
Performance Optimization Expert
Specialized in optimizing React Native and Expo applications for production. Expert in reducing bundle size, improving render performance, optimizing memory usage, and eliminating jank.
What I Know
Bundle Size Optimization
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
- Remove unused dependencies
- Use Hermes engine (Android)
- Enable code minification and obfuscation
- Tree shaking for unused code elimination
- Lazy load heavy screens and components
- Optimize asset sizes (images, fonts)
Hermes Configuration
// app.json (Expo)
{
"expo": {
"jsEngine": "hermes", // Faster startup, smaller bundle
"ios": {
"jsEngine": "hermes"
},
"android": {
"jsEngine": "hermes"
}
}
}
Rendering Performance
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>
List Performance (FlatList/SectionList)
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
/>
);
}
Image Optimization
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 }}
/>
Memory Management
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();
Navigation Performance
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>
Startup Time Optimization
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 />;
}
Animation Performance
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>
);
}
When to Use This Skill
Ask me when you need help with:
- Reducing app bundle size
- Optimizing FlatList/SectionList performance
- Fixing memory leaks
- Improving app startup time
- Eliminating jank and frame drops
- Optimizing image loading and caching
- Reducing component re-renders
- Implementing lazy loading
- Optimizing navigation performance
- Analyzing performance bottlenecks
- Using React.memo, useMemo, useCallback effectively
- Implementing 60fps animations
Performance Monitoring
React Native Performance Monitor
// In app, shake device → Show Perf Monitor
// Shows:
// - JS frame rate
// - UI frame rate
// - RAM usage
Production Performance Monitoring
// 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';
Pro Tips & Tricks
1. Profile with React DevTools Profiler
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>
2. Debounce Expensive Operations
import { debounce } from 'lodash';
import { useCallback } from 'react';
function SearchScreen() {
const debouncedSearch = useCallback(
debounce((query) => {
performSearch(query);
}, 300),
[]
);
return (
<TextInput
onChangeText={debouncedSearch}
placeholder="Search..."
/>
);
}
3. Virtualize Long Lists
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}
/>
4. Optimize StyleSheets
// ❌ 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} />
Integration with SpecWeave
Performance Requirements
- Document performance targets in
spec.md(e.g., <2s startup) - Include performance testing in
tasks.mdtest plans - Measure before/after optimization in increment reports
Performance Metrics
- Bundle size: Track in increment completion reports
- Startup time: Measure and document improvements
- FPS: Target 60fps for critical UI interactions
- Memory usage: Set thresholds and monitor
Living Documentation
- Document performance optimization strategies
- Track bundle size trends across increments
- Maintain performance runbooks for common issues