performance-optimization

Performance Optimization Expert (RN 0.83+)

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "performance-optimization" with this command: npx skills add anton-abyzov/specweave/anton-abyzov-specweave-performance-optimization

Performance Optimization Expert (RN 0.83+)

Specialized in optimizing React Native 0.83+ and Expo SDK 54+ applications for production. Expert in Hermes V1, React 19.2 concurrent features, Intersection Observer API, Web Performance APIs, and modern optimization strategies.

What I Know

React Native 0.83 Performance Features

Hermes V1 (Experimental)

  • Next-generation JavaScript engine

  • Improved garbage collection

  • Better startup performance

  • Enhanced debugging with DevTools

  • Enable in metro.config.js:

// metro.config.js module.exports = { transformer: { hermesParser: true, // Enable Hermes V1 parser }, };

React 19.2 Concurrent Features

  • Activity component for state preservation

  • useEffectEvent for stable event handlers

  • Improved concurrent rendering

  • Better memory management during transitions

// Preserve state while hidden (React 19.2) import { Activity } from 'react';

function TabContent({ isActive, children }) { return ( <Activity mode={isActive ? 'visible' : 'hidden'}> {children} </Activity> ); }

Intersection Observer API (Canary)

  • Web-like lazy loading for React Native

  • Visibility detection without scroll events

  • More efficient than manual scroll tracking

import { IntersectionObserver } from 'react-native';

// Lazy load when element enters viewport const observer = new IntersectionObserver((entries) => { entries.forEach((entry) => { if (entry.isIntersecting) { loadContent(); } }); });

Web Performance APIs (Stable)

  • performance.now() for precise timing

  • User Timing API for custom marks

  • PerformanceObserver for monitoring

// Performance measurement const start = performance.now(); await heavyOperation(); const duration = performance.now() - start;

// User Timing API performance.mark('loadStart'); await loadData(); performance.mark('loadEnd'); performance.measure('dataLoad', 'loadStart', 'loadEnd');

// PerformanceObserver const observer = new PerformanceObserver((list) => { list.getEntries().forEach((entry) => { console.log(${entry.name}: ${entry.duration}ms); }); }); observer.observe({ entryTypes: ['measure'] });

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 with depcheck

  • Use Hermes V1 for smaller bytecode

  • Enable code minification and obfuscation

  • Tree shaking for unused code elimination

  • Lazy load heavy screens and components

  • Optimize asset sizes (images, fonts)

  • Use expo-image instead of react-native-fast-image

Hermes Configuration (RN 0.83)

// app.json (Expo SDK 54+) { "expo": { "jsEngine": "hermes", // Default in SDK 54 "ios": { "jsEngine": "hermes" }, "android": { "jsEngine": "hermes" } } }

// For Hermes V1 experimental // metro.config.js module.exports = { transformer: { hermesParser: true, }, };

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 /> ); }

Intersection Observer for Lazy Loading (RN 0.83 Canary)

import { useRef, useEffect, useState } from 'react'; import { View } from 'react-native';

function LazyLoadItem({ onVisible, children }) { const ref = useRef(null); const [isVisible, setIsVisible] = useState(false);

useEffect(() => { if (!ref.current) return;

const observer = new IntersectionObserver(
  ([entry]) => {
    if (entry.isIntersecting &#x26;&#x26; !isVisible) {
      setIsVisible(true);
      onVisible?.();
      observer.disconnect();
    }
  },
  { threshold: 0.1 }
);

observer.observe(ref.current);
return () => observer.disconnect();

}, []);

return ( <View ref={ref}> {isVisible ? children : <Placeholder />} </View> ); }

Image Optimization

expo-image v2 (Recommended for Expo SDK 54+)

// expo-image is the recommended solution for Expo projects import { Image, useImage } from 'expo-image';

// Basic usage with blurhash placeholder function OptimizedImage({ uri, blurhash }) { return ( <Image source={{ uri }} placeholder={{ blurhash }} contentFit="cover" transition={200} style={{ width: 100, height: 100 }} cachePolicy="memory-disk" // Aggressive caching /> ); }

// Imperative loading with useImage hook (v2) function PreloadedImage({ uri }) { const image = useImage(uri, { onError: (error) => console.error('Image load failed:', error), });

if (!image) { return <ActivityIndicator />; }

return ( <Image source={image} style={{ width: image.width / 2, height: image.height / 2 }} contentFit="cover" /> ); }

Fast Image for Bare RN Projects

// For bare React Native projects without Expo // 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, cache: FastImage.cacheControl.immutable }} 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&#x26;h=200' }} style={{ width: 100, height: 100 }} />

// Use local images when possible (bundled) <Image source={require('./assets/logo.png')} />

// Progressive loading with blurhash import { Image } from 'expo-image';

<Image source={{ uri: imageUrl }} placeholder={{ blurhash: 'LGF5]+Yk^6#M@-5c,1J5@[or[Q6.' }} contentFit="cover" transition={300} 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

  • Configuring Hermes V1 for better performance

  • Using React 19.2 Activity component for state preservation

  • Implementing Intersection Observer for lazy loading

  • Using Web Performance APIs for profiling

  • Migrating to expo-image v2

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>

  1. 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..." /> ); }

  1. 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} />

  1. 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.md test 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

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

technical-writing

No summary provided by upstream source.

Repository SourceNeeds Review
General

spec-driven-brainstorming

No summary provided by upstream source.

Repository SourceNeeds Review
General

kafka-architecture

No summary provided by upstream source.

Repository SourceNeeds Review