Skip to content
Development
Skill

/rn-performance

Diagnosing and fixing React Native performance problems. Use for slow lists, janky animations, slow startup, excessive re-renders, or a sluggish app: FlashList, Reanimated, images, startup, profiling.

From plugin
claude-code-react-native
318 skills4 agents
Install
$ npx -y skills add AnilBurcu/claude-code-react-native --skill rn-performance --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/rn-performance

Context preview

The summary Claude sees to decide when to auto-load this skill.

Diagnosing and fixing React Native performance problems. Use for slow lists, janky animations, slow startup, excessive re-renders, or a sluggish app: FlashList, Reanimated, images, startup, profiling.

SKILL.md

rn-performance.SKILL.md
name: rn-performance
description: Diagnosing and fixing React Native performance problems. Use for slow lists, janky animations, slow startup, excessive re-renders, or a sluggish app: FlashList, Reanimated, images, startup, profiling.
user-invocable: false

Performance triage

Two rules before touching code

1. Profile release builds. Dev mode runs a different engine configuration with dev-only checks; its performance characteristics are fiction. `npx expo run:ios --configuration Release` or `npx expo run:android --variant release` before believing any measurement. 2. Name the thread. Everything slow is one of: JS thread saturated, UI thread saturated, or waiting on IO. The fix lives on the thread that is busy, and the profiler tells you which one it is; guessing does not.

Lists

  • Long scrollable content belongs in FlashList (or at minimum FlatList), never a ScrollView with `.map()`. ScrollView mounts everything immediately; past a few dozen items that is the entire problem.
  • renderItem must be a stable component reference, not an inline closure creating new component trees per render. Extract it, memo it, and keep item props primitive where possible.
  • keyExtractor must return stable ids. Index keys plus insertions equals recycled rows showing the wrong content and pointless re-renders.
  • Images inside rows: fixed dimensions, a recycling-aware image component, and correctly sized source assets. Decoding a 4000px image into a 80px avatar slot burns CPU and memory for nothing.
  • Measure list health with the blank-area metric while scrolling fast, and by watching JS FPS in the perf monitor, not by feel on a simulator. Simulators lie in both directions.

Re-renders

  • Subscribe narrowly. With zustand and friends, select the smallest slice (`useStore(s => s.count)`), never the whole store object. With context, split fast-changing values out of the provider that wraps the world.
  • memo the expensive leaves (list rows, chart bodies, anything with heavy trees), then make sure their props are actually stable, because memo with a fresh object or closure prop every render is a no-op with extra steps.
  • useCallback and useMemo are for identity stability where a memoized child or an effect depends on it, not decoration for every function in the file.
  • To find offenders, use the React DevTools profiler and look for wide flame bars re-rendering on interactions that should be local. One misplaced subscription at the screen root regularly accounts for the whole problem.

Animations and gestures

  • Reanimated worklets run on the UI thread; that is the entire point. An animation that calls back into JS every frame (runOnJS in a hot path, setState per frame) forfeits it.
  • Animate transform and opacity. Animating layout properties (width, height, padding) forces layout every frame.
  • Gesture-driven interactions belong in gesture-handler plus reanimated, not in PanResponder, which routes every move event over to JS.

Startup

  • Defer everything deferable: analytics init, remote config fetch, heavy SDK setup can start after first paint instead of before it.
  • Watch top-level imports. A barrel file that imports the whole app to render the splash screen drags the entire dependency graph into startup. Import concretely and lazily require the rare heavy module.
  • Fonts and lottie files add up; load what the first screen needs, not the full brand kit.
  • Measure TTI on a mid-range Android device, cold start, release build. iPhone-simulator startup times are marketing numbers.

JS thread stalls

  • JSON.parse of megabyte payloads during interaction. Chunk it, move it behind InteractionManager, or trim the payload server-side.
  • Synchronous storage reads in hot paths. Batch and cache; storage is IO wearing a synchronous costume.
  • console.log in release builds costs real time on hot paths; strip or gate it.
  • Date/number formatting in render for every row: precompute at data-massage time, once.

When asked to "make it faster" with no specifics

Do this in order: turn on the performance monitor, reproduce the complaint, identify the busy thread, profile that thread, fix the top item only, re-measure, repeat. One measured fix at a time beats ten speculative ones, and the re-measure step is what keeps optimization honest.

Read more
Ships withclaude-code-react-native

Claude knows React. It doesn't know why your pod install just failed. A Claude Code plugin that adds the React Native knowledge you only get from shipping apps: build failure triage, SDK upgrades that don't eat a weekend, Hermes crash decoding, push

Get the whole plugin

Other skills on claude-code-react-native.