Summary
This skill turns “the app feels slow/janky” into a measured, repeatable, and shippable optimisation program for Expo-managed React Native apps.
Non‑negotiables:
- Optimise against user-visible KPIs (startup/TTI, scroll FPS, navigation responsiveness, memory growth, p95 network latency).
- Profile in production-like builds (release / profile / debugOptimized) — not in dev mode.
- Make one change at a time, re-measure, and keep a rollback path.
When to use
Use when you need to:
- Fix slow startup, “white screen”, or delayed time-to-interactive.
- Fix scroll jank, dropped frames, sluggish taps, or slow transitions.
- Reduce memory growth, crashes under pressure, or image/video bloat.
- Reduce OTA update size, JS bundle size, or Android binary size.
- Add regression prevention: perf budgets + CI gates + production monitoring.
When NOT to use
Don’t use this skill to:
- Prematurely micro-optimise already-smooth screens with no KPI regression.
- Make changes without a reproducible scenario and a baseline.
- “Optimise” by switching libraries blindly (measure first).
Inputs
- Repo (Expo managed or CNG/prebuild), ideally with:
package.json
app.json / app.config.*
metro.config.js (if present)
babel.config.*
eas.json (if using EAS)
- A concrete report of the problem:
- Device(s), OS versions, and which flow feels slow.
- Steps to reproduce (or a screen name if using Expo Router).
If details are missing, infer as much as possible from the repo and propose a minimal repro script.
Outputs
Deliver both:
- Perf audit report (see template in
assets/templates/perf-audit-report-template.md):
- KPIs + budgets
- Baseline measurements (device + build type)
- Root cause hypothesis + evidence
- Fix plan (ordered by ROI / risk)
- Before/after measurements
- Code changes (PR-ready) implementing the top fixes, plus:
- Updated perf budgets (if needed)
- CI gate(s) for bundle/update size at minimum
Tooling assumptions
You can use:
- Expo CLI (
npx expo …), EAS CLI (eas …) where available.
- React Native DevTools (Performance + Memory panels) for JS-level analysis.
- Native profilers (Android Studio, Xcode Instruments) for CPU/memory/UI tracing.
You should prefer:
- Release/profile builds for measurement.
- Same device class and same scenario script for before/after.
The optimisation workflow (high level)
- Define KPIs + budgets (3–6 metrics max). Pick what users feel.
- Create a repeatable scenario (startup, list scroll, key navigation, etc.).
- Measure baseline in a production-like build.
- Classify the bottleneck domain:
- Startup/bundle
- JS thread
- UI thread
- Lists/images
- Memory
- Network/background
- Apply targeted fixes (smallest change, highest ROI first).
- Re-measure. Keep only changes with KPI wins.
- Add regression control (budgets + CI gates + monitoring).
Detailed playbook
Phase 0 — Establish reality (no guesswork)
0.1 Identify versions and architecture
- Expo SDK version, React Native version, React version.
- New Architecture status (mandatory in newer SDKs).
- JS engine (Hermes/JSC/V8), OTA updates usage (
expo-updates).
- Major perf-sensitive libs: navigation (Expo Router/React Navigation), lists (FlashList), animation (Reanimated), images (
expo-image).
0.2 Choose KPIs (pick 3–6)
Suggested defaults:
- Cold start: time-to-first-render and/or time-to-interactive
- Scroll: dropped frames / FPS on the heaviest list
- Navigation: p95 screen transition time for a representative flow
- Memory: steady-state RSS after repeating a navigation loop 5–10×
- Network: p95 API latency on a key endpoint
Record budgets as numbers (not “fast”). See references/00-principles-and-kpis.md.
0.3 Choose build type for measuring
- Prefer store-equivalent Release.
- If you need debuggability, use Android “profileable” builds, iOS Instruments, or Expo’s
debugOptimized where applicable.
Phase 1 — Baseline measurement (release-build discipline)
1.1 Baseline checklist (must pass)
- Dev mode off.
- No remote JS debugging.
- Same device, same OS version, same network conditions.
- Warm vs cold start explicitly noted.
1.2 Capture traces and numbers
- React Native DevTools:
- Performance trace (JS execution + React tracks + network events)
- Heap snapshot (if memory suspected)
- Native tools:
- Android Studio System Trace for jank attribution
- Xcode Instruments (Time Profiler / Allocations / Leaks)
Store raw artefacts (trace files, screenshots) alongside your report.
Phase 2 — Decide the bottleneck domain
Use this decision rubric:
- Startup slow: long splash, white screen, slow first render → startup/bundle.
- Taps lag / transitions slow but scrolling OK → JS thread or navigation.
- Scroll stutters even with little JS work → UI thread or list/render cost.
- Memory climbs over time → leak / image/video pressure.
- Everything waits on API → network/caching.
Phase 3 — Apply high-ROI fixes by domain
A) Startup & bundle
Do in this order:
- Stop doing work before first paint
- Gate only critical assets (fonts, tiny config) and hide splash ASAP.
- Confirm Hermes
- Make it explicit in app config if necessary.
- If you use OTA updates, ensure runtime compatibility when engine/bytecode changes.
- Shrink JS evaluation
- Prefer ESM imports, avoid breaking tree shaking.
- Consider Metro
inlineRequires (validate side effects!).
- Control OTA payloads
- Configure update asset inclusion/exclusion and verify assets.
- Android size knobs (measure trade-offs)
- Enable R8 minify + resource shrinking.
- Treat bundle compression as a measured toggle (smaller APK vs slower startup).
See references/02-startup-bundle-ota.md.
B) JS thread stalls (renders + computation)
High ROI:
- Remove production
console.*.
- Defer heavy work with
InteractionManager / requestAnimationFrame.
- Reduce re-renders:
- Stabilise props, split context, memoise hot rows.
- Consider React Compiler (branch rollout + profiling + easy rollback).
See references/03-rendering-js-ui.md.
C) UI thread / rendering / animations
High ROI:
- Prefer native-driven transitions (native stack /
react-native-screens).
- Avoid expensive UI operations on animated frames:
- Alpha compositing, heavy shadows, animating image size.
- Use native-driver animations where possible; for complex gestures prefer Reanimated worklets.
See references/03-rendering-js-ui.md.
D) Lists, images, and media
High ROI:
- Fix list fundamentals:
- Stable keys, avoid re-render storms, tune render window.
- Add
getItemLayout when item heights are known.
- If still janky: evaluate FlashList for large/complex feeds.
- Move image-heavy UIs to
expo-image with caching + placeholders.
- For replay-heavy video: use
expo-video caching with a storage policy.
See references/04-lists-images-media.md.
E) Memory leaks / pressure
High ROI:
- Reproduce with a navigation stress loop.
- Take JS heap snapshots (before/after) to spot retained graphs.
- If JS heap stable but RSS grows: switch to native allocation tools.
See references/01-profiling-toolchain.md.
F) Network & background work
High ROI:
- Prevent refetch storms: cache + dedupe + prefetch.
- Use platform-appropriate background scheduling (best effort) for sync.
See references/05-network-background.md.
Phase 4 — Regression control (the “next level”)
Minimum viable regression control:
- Budget file committed (bundle size + a few KPI thresholds).
- CI gate that fails on obvious regressions (bundle/update size growth).
- Production monitoring (crash + perf traces) with symbolication.
See references/06-ci-regression.md.
Common pitfalls (things this skill forbids)
- Benchmarking in dev mode and trusting the results.
- Making 5 optimisations at once, then not knowing which mattered.
- “Fixing” a symptom (e.g. bigger splash delay) instead of root cause (slow JS eval).
- Turning on size flags (bundle compression, aggressive shrinking) without measuring startup and runtime.
Fast checklist
References
Start here:
references/00-principles-and-kpis.md
references/01-profiling-toolchain.md
references/02-startup-bundle-ota.md
references/03-rendering-js-ui.md
references/04-lists-images-media.md
references/06-ci-regression.md
External links: see references/resources.md.
1---2name: optimising-expo-react-native-performance3description: Diagnose, improve, and prevent performance regressions in Expo-based React Native apps using release-build profiling, KPI budgets, and targeted fixes across startup, rendering, lists, images, memory, and networking.4license: MIT5---6
7## Summary
8
9This skill turns “the app feels slow/janky” into a **measured**, **repeatable**, and **shippable** optimisation program for Expo-managed React Native apps.
10
11Non‑negotiables:
12- Optimise against **user-visible KPIs** (startup/TTI, scroll FPS, navigation responsiveness, memory growth, p95 network latency).
13- Profile in **production-like builds** (release / profile / debugOptimized) — **not** in dev mode.
14- Make **one change at a time**, re-measure, and keep a rollback path.
15
16## When to use
17
18Use when you need to:
19- Fix **slow startup**, “white screen”, or delayed time-to-interactive.
20- Fix **scroll jank**, dropped frames, sluggish taps, or slow transitions.
21- Reduce **memory growth**, crashes under pressure, or image/video bloat.
22- Reduce **OTA update size**, JS bundle size, or Android binary size.
23- Add **regression prevention**: perf budgets + CI gates + production monitoring.
24
25## When NOT to use
26
27Don’t use this skill to:
28- Prematurely micro-optimise already-smooth screens with no KPI regression.
29- Make changes without a reproducible scenario and a baseline.
30- “Optimise” by switching libraries blindly (measure first).
31
32## Inputs
33
34- Repo (Expo managed or CNG/prebuild), ideally with:
35 - `package.json`
36 - `app.json` / `app.config.*`
37 - `metro.config.js` (if present)
38 - `babel.config.*`
39 - `eas.json` (if using EAS)
40- A concrete report of the problem:
41 - Device(s), OS versions, and which flow feels slow.
42 - Steps to reproduce (or a screen name if using Expo Router).
43
44If details are missing, infer as much as possible from the repo and propose a minimal repro script.
45
46## Outputs
47
48Deliver **both**:
491) **Perf audit report** (see template in `assets/templates/perf-audit-report-template.md`):
50 - KPIs + budgets
51 - Baseline measurements (device + build type)
52 - Root cause hypothesis + evidence
53 - Fix plan (ordered by ROI / risk)
54 - Before/after measurements
552) **Code changes** (PR-ready) implementing the top fixes, plus:
56 - Updated perf budgets (if needed)
57 - CI gate(s) for bundle/update size at minimum
58
59## Tooling assumptions
60
61You can use:
62- Expo CLI (`npx expo …`), EAS CLI (`eas …`) where available.
63- React Native DevTools (Performance + Memory panels) for JS-level analysis.
64- Native profilers (Android Studio, Xcode Instruments) for CPU/memory/UI tracing.
65
66You should prefer:
67- **Release/profile builds** for measurement.
68- **Same device class** and **same scenario script** for before/after.
69
70## The optimisation workflow (high level)
71
721) **Define KPIs + budgets** (3–6 metrics max). Pick what users feel.
732) **Create a repeatable scenario** (startup, list scroll, key navigation, etc.).
743) **Measure baseline in a production-like build**.
754) **Classify the bottleneck domain**:
76 - Startup/bundle
77 - JS thread
78 - UI thread
79 - Lists/images
80 - Memory
81 - Network/background
825) Apply **targeted fixes** (smallest change, highest ROI first).
836) **Re-measure**. Keep only changes with KPI wins.
847) Add **regression control** (budgets + CI gates + monitoring).
85
86## Detailed playbook
87
88### Phase 0 — Establish reality (no guesswork)
89
90**0.1 Identify versions and architecture**
91- Expo SDK version, React Native version, React version.
92- New Architecture status (mandatory in newer SDKs).
93- JS engine (Hermes/JSC/V8), OTA updates usage (`expo-updates`).
94- Major perf-sensitive libs: navigation (Expo Router/React Navigation), lists (FlashList), animation (Reanimated), images (`expo-image`).
95
96**0.2 Choose KPIs (pick 3–6)**
97Suggested defaults:
98- Cold start: **time-to-first-render** and/or **time-to-interactive**
99- Scroll: dropped frames / FPS on the heaviest list
100- Navigation: p95 screen transition time for a representative flow
101- Memory: steady-state RSS after repeating a navigation loop 5–10×
102- Network: p95 API latency on a key endpoint
103
104Record budgets as numbers (not “fast”). See `references/00-principles-and-kpis.md`.
105
106**0.3 Choose build type for measuring**
107- Prefer store-equivalent **Release**.
108- If you need debuggability, use Android “profileable” builds, iOS Instruments, or Expo’s `debugOptimized` where applicable.
109
110### Phase 1 — Baseline measurement (release-build discipline)
111
112**1.1 Baseline checklist (must pass)**
113- Dev mode off.
114- No remote JS debugging.
115- Same device, same OS version, same network conditions.
116- Warm vs cold start explicitly noted.
117
118**1.2 Capture traces and numbers**
119- React Native DevTools:
120 - Performance trace (JS execution + React tracks + network events)
121 - Heap snapshot (if memory suspected)
122- Native tools:
123 - Android Studio System Trace for jank attribution
124 - Xcode Instruments (Time Profiler / Allocations / Leaks)
125
126Store raw artefacts (trace files, screenshots) alongside your report.
127
128### Phase 2 — Decide the bottleneck domain
129
130Use this decision rubric:
131- **Startup slow**: long splash, white screen, slow first render → startup/bundle.
132- **Taps lag / transitions slow** but scrolling OK → JS thread or navigation.
133- **Scroll stutters** even with little JS work → UI thread or list/render cost.
134- **Memory climbs over time** → leak / image/video pressure.
135- **Everything waits on API** → network/caching.
136
137### Phase 3 — Apply high-ROI fixes by domain
138
139#### A) Startup & bundle
140Do in this order:
1411) **Stop doing work before first paint**
142 - Gate only critical assets (fonts, tiny config) and hide splash ASAP.
1432) **Confirm Hermes**
144 - Make it explicit in app config if necessary.
145 - If you use OTA updates, ensure runtime compatibility when engine/bytecode changes.
1463) **Shrink JS evaluation**
147 - Prefer ESM imports, avoid breaking tree shaking.
148 - Consider Metro `inlineRequires` (validate side effects!).
1494) **Control OTA payloads**
150 - Configure update asset inclusion/exclusion and verify assets.
1515) **Android size knobs (measure trade-offs)**
152 - Enable R8 minify + resource shrinking.
153 - Treat bundle compression as a measured toggle (smaller APK vs slower startup).
154
155See `references/02-startup-bundle-ota.md`.
156
157#### B) JS thread stalls (renders + computation)
158High ROI:
1591) Remove production `console.*`.
1602) Defer heavy work with `InteractionManager` / `requestAnimationFrame`.
1613) Reduce re-renders:
162 - Stabilise props, split context, memoise hot rows.
163 - Consider **React Compiler** (branch rollout + profiling + easy rollback).
164
165See `references/03-rendering-js-ui.md`.
166
167#### C) UI thread / rendering / animations
168High ROI:
1691) Prefer native-driven transitions (native stack / `react-native-screens`).
1702) Avoid expensive UI operations on animated frames:
171 - Alpha compositing, heavy shadows, animating image size.
1723) Use native-driver animations where possible; for complex gestures prefer Reanimated worklets.
173
174See `references/03-rendering-js-ui.md`.
175
176#### D) Lists, images, and media
177High ROI:
1781) Fix list fundamentals:
179 - Stable keys, avoid re-render storms, tune render window.
180 - Add `getItemLayout` when item heights are known.
1812) If still janky: evaluate FlashList for large/complex feeds.
1823) Move image-heavy UIs to `expo-image` with caching + placeholders.
1834) For replay-heavy video: use `expo-video` caching with a storage policy.
184
185See `references/04-lists-images-media.md`.
186
187#### E) Memory leaks / pressure
188High ROI:
1891) Reproduce with a navigation stress loop.
1902) Take JS heap snapshots (before/after) to spot retained graphs.
1913) If JS heap stable but RSS grows: switch to native allocation tools.
192
193See `references/01-profiling-toolchain.md`.
194
195#### F) Network & background work
196High ROI:
1971) Prevent refetch storms: cache + dedupe + prefetch.
1982) Use platform-appropriate background scheduling (best effort) for sync.
199
200See `references/05-network-background.md`.
201
202### Phase 4 — Regression control (the “next level”)
203
204Minimum viable regression control:
205- **Budget file** committed (bundle size + a few KPI thresholds).
206- **CI gate** that fails on obvious regressions (bundle/update size growth).
207- **Production monitoring** (crash + perf traces) with symbolication.
208
209See `references/06-ci-regression.md`.
210
211## Common pitfalls (things this skill forbids)
212
213- Benchmarking in dev mode and trusting the results.
214- Making 5 optimisations at once, then not knowing which mattered.
215- “Fixing” a symptom (e.g. bigger splash delay) instead of root cause (slow JS eval).
216- Turning on size flags (bundle compression, aggressive shrinking) without measuring startup and runtime.
217
218## Fast checklist
219
220- [ ] KPIs chosen (3–6) + budgets written down
221- [ ] Baseline measured in production-like build
222- [ ] Bottleneck domain identified with evidence
223- [ ] One fix at a time + before/after numbers
224- [ ] At least one regression gate added (bundle/update size)
225- [ ] Monitoring configured (crash + perf)
226
227## References
228
229Start here:
230- `references/00-principles-and-kpis.md`
231- `references/01-profiling-toolchain.md`
232- `references/02-startup-bundle-ota.md`
233- `references/03-rendering-js-ui.md`
234- `references/04-lists-images-media.md`
235- `references/06-ci-regression.md`
236
237External links: see `references/resources.md`.