- name
- optimising-expo-react-native-performance
- description
- 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.
- version
- 2.0.0
- license
- MIT
- tags
- ["react-native","expo","performance","profiling","optimisation","hermes","metro","flashlist","reanimated","eas"]
## 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**:
1) **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
2) **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)
1) **Define KPIs + budgets** (3–6 metrics max). Pick what users feel.
2) **Create a repeatable scenario** (startup, list scroll, key navigation, etc.).
3) **Measure baseline in a production-like build**.
4) **Classify the bottleneck domain**:
- Startup/bundle
- JS thread
- UI thread
- Lists/images
- Memory
- Network/background
5) Apply **targeted fixes** (smallest change, highest ROI first).
6) **Re-measure**. Keep only changes with KPI wins.
7) 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:
1) **Stop doing work before first paint**
- Gate only critical assets (fonts, tiny config) and hide splash ASAP.
2) **Confirm Hermes**
- Make it explicit in app config if necessary.
- If you use OTA updates, ensure runtime compatibility when engine/bytecode changes.
3) **Shrink JS evaluation**
- Prefer ESM imports, avoid breaking tree shaking.
- Consider Metro `inlineRequires` (validate side effects!).
4) **Control OTA payloads**
- Configure update asset inclusion/exclusion and verify assets.
5) **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:
1) Remove production `console.*`.
2) Defer heavy work with `InteractionManager` / `requestAnimationFrame`.
3) 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:
1) Prefer native-driven transitions (native stack / `react-native-screens`).
2) Avoid expensive UI operations on animated frames:
- Alpha compositing, heavy shadows, animating image size.
3) 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:
1) Fix list fundamentals:
- Stable keys, avoid re-render storms, tune render window.
- Add `getItemLayout` when item heights are known.
2) If still janky: evaluate FlashList for large/complex feeds.
3) Move image-heavy UIs to `expo-image` with caching + placeholders.
4) 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:
1) Reproduce with a navigation stress loop.
2) Take JS heap snapshots (before/after) to spot retained graphs.
3) If JS heap stable but RSS grows: switch to native allocation tools.
See `references/01-profiling-toolchain.md`.
#### F) Network & background work
High ROI:
1) Prevent refetch storms: cache + dedupe + prefetch.
2) 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
- [ ] KPIs chosen (3–6) + budgets written down
- [ ] Baseline measured in production-like build
- [ ] Bottleneck domain identified with evidence
- [ ] One fix at a time + before/after numbers
- [ ] At least one regression gate added (bundle/update size)
- [ ] Monitoring configured (crash + perf)
## 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`.
Ver no GitHub