Performance Best Practices
A condensed summary of the 25 most important best practices drawn from every page in this section.
Search across all documentation pages
A condensed summary of the 25 most important best practices drawn from every page in this section.
Measure on mid-tier Android first: Pixel 6a class exposes jank, memory pressure, and slow storage that iPhone Pro hides - if it passes reference Android, iOS is usually fine; the reverse is false.
Define TTI before optimizing: Cold start → primary tab interactive (not splash hide alone) - document the gate in docs/performance-budgets.md with a reference device.
Publish budgets with named owners: Bundle MB, TTI seconds, FPS floors, and memory ceilings - anonymous budgets are ignored in crunch week; see Performance Budget Rules.
Profile release-like builds: Expo Go and dev client timings are not production - use EAS preview or release variants for TTI, bundle, and Flashlight scores.
Separate JS FPS from UI FPS: JS at 0 means taps queue even when UI FPS looks fine - scroll jank on feeds often involves both threads.
Instrument TTI with marks: performance.now() at root layout and primary screen interactive - ship analytics events in production, not only console.info.
Keep root _layout imports minimal: Heavy SDKs, charts, and editors belong in lazy routes - every root import is on the cold-start critical path.
Hold splash until first meaningful frame: Avoid splash hide → full-screen spinner → content; load one body font for TTI, defer the rest.
Defer post-TTI work with InteractionManager: Analytics, migrations, remote config, and secondary prefetch run after animations - cancel tasks on unmount.
Analyze bundle size in CI: react-native-bundle-visualizer on --dev false bundles - fail PRs over budget without ADR exception.
Lazy-load heavy Expo Router screens: React.lazy + Suspense for reports, maps, and editors - confirm treemap blocks leave the main bundle.
Use narrow package imports: Per-icon and per-function paths - full lodash and barrel re-exports dominate treemaps.
Record React DevTools Profiler on navigation: Tab switch, stack push/pop, modal open - fix unstable context and screenOptions before memoizing leaves.
Stabilize list parents before row memo: useCallback renderItem, module-scope keyExtractor, and extraData for selection - see Lists Best Practices.
Default to FlatList for growing feeds: ScrollView mounts every child - switch at ~20 rows or any pagination; evaluate FlashList when profiling proves mount cost.
Run Hermes sampling on hot scenarios: Fast fling, tab switch, cold start - export .cpuprofile to speedscope when JS CPU spikes without obvious React commits.
Flashlight score the primary scroll scenario: BAM aggregated score on reference Android - compare iterations across releases, not one demo on a flagship.
Build a two-device matrix: Mid-tier gate + budget floor device - flagship scores are informational only.
Cap list memory and paginate with eviction: Unbounded data arrays are the #1 session RAM leak - ring buffer or max items with persisted cursor.
Clean up listeners in every useEffect: AppState, NetInfo, navigation addListener, intervals - missing cleanup doubles work and retains closures.
Clear caches on logout: TanStack Query clear(), MMKV user scope, global image maps - stale cache is a memory and security defect.
Soak-test memory for 10 minutes: Realistic scroll, stack navigation, background/foreground - leaks appear late, not in 30-second smoke tests.
Tune windowSize only after hygiene: Lower reduces memory; too low causes blank rows - profile with Flashlight before changing defaults.
Link regressions to rollback policy: TTI > 15%, score drop > 10 points, or bundle overrun triggers release review - document in Performance Budget Rules.
Fix measurement before micro-optimizations: Repro script → DevTools commits → Hermes hotspots → bundle treemap - not random useMemo on presentational chips.
Written budgets + reference Android device + release preview builds + React DevTools Profiler for navigation + Hermes sampling for JS hotspots + Flashlight for scroll scores + Metro visualizer in CI.
Lists Best Practices first - keys, memo, renderItem. Then React DevTools Profiler. Then Hermes Sampling Profiler if JS CPU remains high.
No - native shell, module graph, and dev overhead differ. Ship perf sign-off on EAS preview/production artifacts only.
Performance Budget Rules owns thresholds, CI gates, and owners. This section owns measurement tools and fix patterns.
FlatList for short and medium feeds with correct hygiene. FlashList when profiling shows recycling/mount cost dominates - benchmark with Flashlight before migrating.
Stack versions: This page was written for React 19.2.3, React Native 0.86.0, and Expo SDK 57 (
expo~57.0.4).
Reviewed by Chris St. John·Last updated Jul 19, 2026