skills/tristanmanchester/agent-skills/optimising-expo-react-native-performance

optimising-expo-react-native-performance

SKILL.md

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.

Weekly Installs
13
First Seen
14 days ago
Installed on
opencode13
gemini-cli13
github-copilot13
codex13
amp13
kimi-cli13