Rn performance audit
Skill almasumdev/awesome-react-native-agent-skills/.github/skills/performance/rn-performance-audit
Expert guidance on auditing React Native performance, including Hermes sampling profiler, re-render detection, and list performance. Use when asked about slow screens, jank, dropped frames, or excessive re-renders.From its SKILL.md
npx -y skills add almasumdev/awesome-react-native-agent-skills --skill rn-performance-auditAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
- 1 stars1 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.1 KB, 963 tokens by cl100k_base, as published. Nobody here has run it
React Native Performance Audit
Instructions
Measure before optimizing. The cheapest wins almost always come from fixing re-renders and list configuration, not from native code changes.
1. Hermes Sampling Profiler
Hermes ships a built-in sampler. Record a trace from a dev build:
import { startSamplingProfiler, stopSamplingProfiler } from 'react-native/Libraries/Utilities/HMRClient';
// In dev, connect via Chrome DevTools -> "Performance" tab on the Hermes target.
Or via the command line:
adb shell "run-as com.example.app killall -SIGUSR1 com.example.app"
# Pull /data/data/com.example.app/cache/sampling-profiler-trace-*.cpuprofile
Load the .cpuprofile in Chrome DevTools. Focus on:
- Functions with > 5% total time.
- Deep stacks inside
commitRoot/reconcileChildren(indicates too much work per commit). - Long runs of
JSON.parseorrequireduring startup.
2. Detecting Re-renders
Enable the React DevTools "Highlight updates when components render" option during manual testing.
Add a tiny hook to log why a component re-rendered:
import { useRef, useEffect } from 'react';
export function useWhyDidYouRender<T extends Record<string, unknown>>(name: string, props: T) {
const prev = useRef<T>(props);
useEffect(() => {
const changed: Record<string, { from: unknown; to: unknown }> = {};
for (const k of Object.keys({ ...prev.current, ...props })) {
if (prev.current[k] !== props[k]) {
changed[k] = { from: prev.current[k], to: props[k] };
}
}
if (Object.keys(changed).length) {
console.log(`[render] ${name}`, changed);
}
prev.current = props;
});
}
Common fixes:
- Wrap row components with
React.memoand use stable keys. - Replace inline callbacks with
useCallback. - Replace inline object/array props with
useMemoor module-level constants. - Use Zustand selectors or
useSyncExternalStoreso components subscribe only to what they render.
3. List Performance
For any FlashList or FlatList:
<FlashList
data={data}
keyExtractor={(x) => x.id}
estimatedItemSize={96}
drawDistance={400}
renderItem={renderItem}
getItemType={(x) => x.variant}
removeClippedSubviews
/>
Checklist:
estimatedItemSizematches the measured row height within ~15%.renderItemis defined outside the list or wrapped withuseCallback.- Row images use
expo-imagewithrecyclingKey(seern-images). - Heavy row children are deferred with
InteractionManager.runAfterInteractionsor Suspense boundaries.
4. Startup Time
- Enable
inlineRequiresinmetro.config.jsso modules load lazily. - Defer non-critical providers until after the first screen renders.
- Avoid synchronous work (JSON parsing, crypto) in
App.tsxmodule scope. - Measure with
performance.now()around the root render:
const t0 = performance.now();
// after first useEffect in App:
console.log('ttr', performance.now() - t0);
5. Navigation Transitions
- Prefer
native-stackover@react-navigation/stackfor 60fps transitions. - Use
useIsFocused()to gate expensive work off-screen. - Avoid mounting large tab screens eagerly; enable
lazy: trueon the tab navigator.
6. Animations
See rn-animations. A jumpy animation almost always means:
- It is running on the JS thread (missing
useAnimatedStyle). - It is animating a layout property.
- It is fighting a re-render storm from a parent.
Checklist
- A Hermes CPU profile was captured for the slow flow and analyzed.
- Row components are
memo-wrapped and callbacks are stable. -
FlashListhas a measuredestimatedItemSizewithin ~15% of reality. - Inline object/array/function props are hoisted or memoised.
- Startup has no synchronous heavy work in module scope.
- Off-screen tabs are lazy; off-screen work is gated by
useIsFocused.
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.