Expert in React Native 0.83+ performance optimization including Hermes V1, React 19.2 concurrent features, Intersection Observer, Web Performance APIs, bundle size reduction, memory management, rendering optimization, FlashList, expo-image v2, memoization, 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, hermes, concurrent rendering.
Optimizes React Native 0.83+ apps using Hermes V1, React 19.2 concurrent features, and Web Performance APIs. Activates when you mention performance issues like slow startup, lag, memory leaks, or large bundle sizes.
/plugin marketplace add anton-abyzov/specweave/plugin install sw-mobile@specweaveThis skill inherits all available tools. When active, it can use any tool Claude has access to.
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.
Hermes V1 (Experimental)
// metro.config.js
module.exports = {
transformer: {
hermesParser: true, // Enable Hermes V1 parser
},
};
React 19.2 Concurrent Features
// 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)
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 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'] });
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 (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,
},
};
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
/>
);
}
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 && !isVisible) {
setIsVisible(true);
onVisible?.();
observer.disconnect();
}
},
{ threshold: 0.1 }
);
observer.observe(ref.current);
return () => observer.disconnect();
}, []);
return (
<View ref={ref}>
{isVisible ? children : <Placeholder />}
</View>
);
}
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&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 }}
/>
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
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.