Performance
Skill Shankulkarni/react-native-ai-plugin/plugins/rn-ai/skills/performance
Use when optimizing app performance -- TTI targets, FlashList, FPS measurement, Hermes, React Compiler, bundle analysis, memo patterns, avoiding re-renders.From its SKILL.md
npx -y skills add Shankulkarni/react-native-ai-plugin --skill performanceAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
4.6 KB, ~1.2k tokens by cl100k_base, as published. Nobody here has run it
Performance
TTI Targets (Time to Interactive)
| TTI | Rating |
|---|---|
| <2s | Excellent |
| 2--4s | Acceptable |
| >4s | Poor -- must fix |
JS Bundle load target: <500ms. Native init target: <500ms.
FPS Targets
| FPS | State |
|---|---|
| 55--60 | Smooth -- target |
| 45--55 | Slight stutter -- investigate |
| 30--45 | Noticeable -- fix before release |
| <30 | Critical |
UI FPS drop = native rendering issue (layout properties animated, heavy views)
JS FPS drop = JS thread blocked (heavy computation, too many re-renders)
Lists -- FlashList over ScrollView
// ❌ ScrollView -- renders ALL items upfront, freezes on long lists
<ScrollView>
{posts.map(p => <PostCard key={p.id} {...p} />)}
</ScrollView>
// ✅ FlashList -- virtualizes, renders only visible items
import { FlashList } from '@shopify/flash-list'
<FlashList
data={posts}
renderItem={({ item }) => <PostCard id={item.id} title={item.title} />}
estimatedItemSize={88}
keyExtractor={item => item.id}
onEndReached={fetchNextPage}
onEndReachedThreshold={0.5}
ListEmptyComponent={<EmptyState />}
ListFooterComponent={isFetchingNextPage ? <ActivityIndicator /> : null}
/>
List Memoization Rules
// ❌ Object prop -- breaks memo, re-renders every time
<FlashList renderItem={({ item }) => <PostCard data={item} onPress={handlePress} />} />
// ✅ Primitive props -- shallow comparison works
<FlashList renderItem={({ item }) => (
<PostCard id={item.id} title={item.title} onPress={handlePress} />
)} />
// ✅ Memoized item
const PostCard = memo(({ id, title, onPress }: PostCardProps) => ...)
// ✅ Hoisted callback -- stable reference
const handlePress = useCallback((id: string) => router.push({ pathname: '/(app)/[id]', params: { id } }), [])
// ✅ Memoized data
const filteredPosts = useMemo(() => posts.filter(p => p.active), [posts])
Heterogeneous Lists
type FeedItem = { type: 'post' | 'ad' | 'story' } & (PostItem | AdItem | StoryItem)
<FlashList
data={feed}
getItemType={item => item.type}
estimatedItemSize={88}
renderItem={({ item }) => {
if (item.type === 'post') return <PostCard {...item} />
if (item.type === 'ad') return <AdCard {...item} />
return <StoryCard {...item} />
}}
/>
Avoid Re-renders
// ❌ Inline object -- new reference every render
<Component style={{ color: 'red' }} options={{ timeout: 1000 }} />
// ✅ Outside component or useMemo
const STYLE = { color: 'red' as const }
const OPTIONS = { timeout: 1000 }
// ❌ Inline function -- new reference every render
<FlatList renderItem={({ item }) => <Item {...item} onPress={() => handlePress(item.id)} />} />
// ✅ useCallback
const renderItem = useCallback(({ item }) => <Item {...item} onPress={handlePress} />, [handlePress])
React Compiler (RN 0.76+, Expo SDK 52+)
Automatically removes memo, useCallback, useMemo where safe.
# Check if your code is compiler-ready
bunx react-compiler-healthcheck@latest
Write compiler-friendly code:
// ✅ Destructure functions at render top
function Screen({ onPress, onChange }: Props) {
// Compiler can optimize these
const handlePress = () => onPress()
const handleChange = (v: string) => onChange(v)
}
// ❌ Dot-access in JSX
<Pressable onPress={() => props.onPress()} />
Hermes
Enabled by default in Expo SDK 52+. Hermes benefits:
- Faster startup (bytecode pre-compilation)
- Lower memory usage
- Better GC
Ensure Hermes is enabled in app.json:
{ "expo": { "jsEngine": "hermes" } }
Animate GPU Properties Only
// ❌ Causes layout recalculation
width.value = withSpring(200)
marginTop.value = withTiming(20)
// ✅ GPU only
transform: [{ scale: scaleValue }]
opacity: opacityValue
Intl at Module Scope
// ❌ Created in render -- expensive
function formatDate(date: Date) {
return new Intl.DateTimeFormat('en-US', { month: 'short' }).format(date)
}
// ✅ Created once at module level
const dateFormatter = new Intl.DateTimeFormat('en-US', { month: 'short' })
function formatDate(date: Date) { return dateFormatter.format(date) }
Bundle Analysis
# Expo Atlas (most accurate for Expo)
EXPO_UNSTABLE_ATLAS=true bunx expo export
# Open .expo/atlas.jsonl in Atlas viewer
# Source map explorer
bunx source-map-explorer dist/index.js
Common bundle issues:
- Barrel exports (
import { x } from 'big-library'loads everything) - Moment.js (use
dayjsinstead) - Duplicate packages (check
bun why <package>)
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.