optimising-expo-react-native-performance
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.jsonapp.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
debugOptimizedwhere 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
getItemLayoutwhen item heights are known.
- If still janky: evaluate FlashList for large/complex feeds.
- Move image-heavy UIs to
expo-imagewith caching + placeholders. - For replay-heavy video: use
expo-videocaching 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
- 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.mdreferences/01-profiling-toolchain.mdreferences/02-startup-bundle-ota.mdreferences/03-rendering-js-ui.mdreferences/04-lists-images-media.mdreferences/06-ci-regression.md
External links: see references/resources.md.