optimising-expo-react-native-performance
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.
What this skill does
## 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
Related in Web Dev
generating-lwc-components
IncludedLightning Web Components with PICKLES methodology and 165-point scoring. Use this skill when the user creates or edits LWC components, builds wire service patterns, or writes Jest tests for LWC. TRIGGER when: user creates/edits LWC components, touches lwc/**/*.js, .html, .css, .js-meta.xml files, or asks about wire service, SLDS, or Jest LWC tests. DO NOT TRIGGER when: Apex classes (use generating-apex), Aura components, or Visualforce.
tanstack-query
IncludedManage server state in React with TanStack Query v5. Set up queries with useQuery, mutations with useMutation, configure QueryClient caching strategies, implement optimistic updates, and handle infinite scroll with useInfiniteQuery. Use when: setting up data fetching in React projects, migrating from v4 to v5, or fixing object syntax required errors, query callbacks removed issues, cacheTime renamed to gcTime, isPending vs isLoading confusion, keepPreviousData removed problems.
document-processor-api
IncludedProcess documents with Nutrient DWS. Use when the user wants to generate PDFs from HTML or URLs, convert Office/images/PDFs, assemble or split packets, OCR scans, extract text/tables/key-value pairs, redact PII, watermark, sign, fill forms, optimize PDFs, or produce compliance outputs like PDF/A or PDF/UA. Triggers include convert to PDF, merge these PDFs, OCR this scan, extract tables, redact PII, sign this PDF, make this PDF/A, or linearize for web delivery.
nutrient-document-processing
IncludedProcess documents with Nutrient DWS. Use when the user wants to generate PDFs from HTML or URLs, convert Office/images/PDFs, assemble or split packets, OCR scans, extract text/tables/key-value pairs, redact PII, watermark, sign, fill forms, optimize PDFs, or produce compliance outputs like PDF/A or PDF/UA. Triggers include convert to PDF, merge these PDFs, OCR this scan, extract tables, redact PII, sign this PDF, make this PDF/A, or linearize for web delivery.
tanstack-query
IncludedManage server state in React with TanStack Query v5. Covers useMutationState, simplified optimistic updates, throwOnError, network mode (offline/PWA), and infiniteQueryOptions. Use when setting up data fetching, fixing v4→v5 migration errors (object syntax, gcTime, isPending, keepPreviousData), or debugging SSR/hydration issues with streaming server components.
accelint-nextjs-best-practices
IncludedNext.js performance optimization and best practices. Use when writing Next.js code (App Router or Pages Router); implementing Server Components, Server Actions, or API routes; optimizing RSC serialization, data fetching, or server-side rendering; reviewing Next.js code for performance issues; fixing authentication in Server Actions; or implementing Suspense boundaries, parallel data fetching, or request deduplication.