Org Skills

react-native-performance

Diagnose and fix React Native performance — slow app startup, dropped frames, janky FlatList/FlashList scrolling, laggy animations and gestures, JS thread blocking, large bundles, memory leaks, and image loading.

Download .zip Raw Source
When agents use itUse this whenever the user says a React Native or Expo app is slow, stutters, has low FPS, takes long to open, or asks about Reanimated, Gesture Handler, FlashList, Hermes, the New Architecture's performance, profiling with Flipper/Perfetto/Xcode Instruments, or app size. Also apply it when reviewing RN code that renders long lists, animates with Animated API on the JS thread, or does heavy work in render.

Install

Copilot (VS Code, Visual Studio, Copilot CLI and github.com) reads skills from the repository — commit them so the whole team gets them.

npx skills add AGCO-Global/org-skills --skill react-native-performance -a github-copilot
# or with the org installer (adds .github/skills/react-native-performance):
npx -y github:AGCO-Global/org-skills add skill react-native-performance

Uses the open skills CLI. Works with Claude Code, Codex, Cursor, Copilot, Gemini CLI, OpenCode, Windsurf and 60+ others — it asks which agent to install into.

npx skills add AGCO-Global/org-skills --skill react-native-performance
# user-level instead of project-level:
npx skills add AGCO-Global/org-skills --skill react-native-performance -g

Installs the frontend-react-native-skills plugin, which bundles all Frontend / React Native skills and keeps them updated.

/plugin marketplace add AGCO-Global/org-skills
/plugin install frontend-react-native-skills@org-skills
# or just this skill, in this repository:
npx skills add AGCO-Global/org-skills --skill react-native-performance -a claude-code

Use Download .zip above, then upload it under Customize → Skills → Upload skill. Team and Enterprise admins can sync this repo as a plugin marketplace instead.

Installs into .agents/skills/, which Codex reads.

npx skills add AGCO-Global/org-skills --skill react-native-performance -a codex

Installs into Cursor's skills folder.

npx skills add AGCO-Global/org-skills --skill react-native-performance -a cursor

Installs into Gemini CLI's skills folder.

npx skills add AGCO-Global/org-skills --skill react-native-performance -a gemini-cli

Any tool with rules, instructions or custom prompts: use Copy SKILL.md above and paste it in. It is plain Markdown.

Commands use your normal git sign-in to GitHub, so they work while the repository is private. Node.js 20+ required.

Skill contents

React Native Performance

Two threads matter: the JS thread (your React code) and the UI thread (native rendering). Anything that blocks the JS thread for > 16 ms drops frames. Most RN performance work is moving work off the JS thread or doing less of it.

1. Measure first — the right tool for each symptom

Symptom Tool Capture
Slow cold start react-native-performance marks, Xcode Instruments (App Launch), Android adb shell am start -W Time to first screen, time to interactive
Dropped frames while scrolling/animating Perf Monitor overlay (dev menu), Perfetto (Android), Instruments Core Animation (iOS) JS FPS vs UI FPS — tells you which thread is the bottleneck
Slow interactions React DevTools Profiler, Hermes sampling profiler (--profile), Flipper/Reactotron Long JS commits, render counts
Big binary / slow OTA npx react-native-bundle-visualizer, EAS build size report Largest JS modules, native libs, asset weights
Memory growth Xcode Memory Graph, Android Studio Profiler Retained images, listeners

Always profile a release build on a low-end real device (an Android from ~3 years ago). Dev mode is 5–10× slower and hides everything.

2. Startup time

  • Hermes on, New Architecture on. Check both; they are the biggest free wins.
  • Defer everything not needed for first paint: analytics init, remote config, non-critical native modules — start them after InteractionManager.runAfterInteractions or on first idle.
  • Lazy-require heavy screens/libraries (React.lazy with Expo Router's automatic splitting, or inline require() in navigation).
  • Reduce root re-renders: one provider tree, memoised context values, no fetching in the root layout that blocks render.
  • Splash: use expo-splash-screen to hold the native splash until fonts/session are ready, then hide once — no flash of unstyled UI.
  • Inline requires (inlineRequires: true in Metro transformer) — on by default in modern templates; verify.

3. Lists — the #1 jank source

  • Use FlashList (@shopify/flash-list) for any list > 50 items. Provide estimatedItemSize. Keep keyExtractor stable.
  • If staying on FlatList: getItemLayout for fixed heights, windowSize 5–10, maxToRenderPerBatch, removeClippedSubviews on Android, initialNumToRender sized to one screen.
  • Row components: memo, no inline closures creating new props, no new Date()/formatting in render — pre-format in the data layer.
  • Images inside rows: fixed dimensions, expo-image with recyclingKey, thumbnails not originals.
  • Never nest a vertical list inside a ScrollView. Use list header/footer components or SectionList.
  • Avoid key = index on reorderable data.

4. Animations and gestures — UI thread only

  • Reanimated 3+ for everything animated. useAnimatedStyle + withSpring/withTiming run on the UI thread; the JS thread can be frozen and the animation still plays.
  • Gesture Handler with Reanimated worklets for drag, swipe, pinch. Never PanResponder.
  • Layout animations via Reanimated Layout/entering/exiting; avoid LayoutAnimation from core.
  • Don't setState per frame. Use shared values; only sync to React state on gesture end (runOnJS once).
  • Animated (core) only with useNativeDriver: true, which supports opacity/transform only — that limitation is why Reanimated is the default.
  • Skia (@shopify/react-native-skia) for complex drawing/charts that would otherwise be hundreds of Views.

5. Rendering and JS thread hygiene

  • Apply the React re-render decision tree (see react-performance): move state down, split contexts, then memo. React Compiler works in RN too.
  • Heavy computation (search, sorting large arrays, parsing) → move to the data layer once, cache with TanStack Query/useMemo, or offload to a worklet/JSI module.
  • Avoid console.log in production paths — strip with babel-plugin-transform-remove-console.
  • Keep the component tree shallow: each View is a native view; flatten wrappers, prefer StyleSheet composition over nested containers.
  • Measure and avoid onLayout cascades that trigger re-renders every frame.

6. Images

  • expo-image everywhere: disk + memory caching, blurhash placeholders, contentFit, priority, prefetch.
  • Serve correctly sized images from a CDN with resizing params — never download 4000 px for a 100 px avatar.
  • cachePolicy="memory-disk" for repeated content; recyclingKey inside lists.
  • Local assets: compress (WebP), and preload critical ones with Asset.loadAsync behind the splash.

7. Network and data

  • Persist the TanStack Query cache (MMKV) so screens render instantly from cache, then refetch.
  • staleTime > 0 for anything that doesn't need to be live; avoid refetch on every focus for expensive endpoints.
  • Batch and paginate; avoid N+1 request patterns in lists.
  • Compress payloads (gzip/brotli), request only fields needed.
  • Use react-native-mmkv not AsyncStorage for anything read during render.

8. Bundle and binary size

  • Analyse with bundle visualizer; remove moment, full lodash, unused icon sets, duplicated polyfills.
  • Enable Proguard/R8 (Android) and bitcode-free, stripped builds (iOS); use App Bundles (.aab).
  • Hermes bytecode precompilation is on in release — confirm.
  • Strip unused locales/fonts; move large media to remote download on demand.
  • Check expo-doctor and dependency count; every native lib adds startup cost.

9. Memory

  • Remove listeners in effect cleanup (AppState, Dimensions, Keyboard, NetInfo, event emitters).
  • Clear timers and cancel in-flight requests on unmount (AbortController).
  • Unbounded caches (in-memory maps of images or records) need eviction.
  • Large base64 strings in state are a leak magnet — store files with expo-file-system and keep URIs.

10. Report format

## Device & build
<model, OS>, release build, New Arch: on/off, Hermes: on/off

## Baseline
JS FPS / UI FPS during <scenario>; cold start ms; bundle MB

## Findings (impact order)
1. <what> — <evidence> — <thread affected>

## Fixes
Minimal diff per finding + expected effect

## Verify
Same scenario, same device, after/before numbers

Anti-patterns to reject on sight

  • ScrollView + .map() over hundreds of items.
  • Animated.Value driven by setState per frame, or any animation on the JS thread.
  • PanResponder for new gesture code.
  • Measuring in debug mode on a simulator.
  • Images without explicit dimensions or with originals rendered as thumbnails.
  • AsyncStorage reads on the render path.
  • Disabling Hermes or the New Architecture to "fix" a crash instead of fixing the dependency.