AboutExpertiseProjectsJourneyBlogContact
Discuss
AboutExpertiseProjectsJourneyBlogContactDiscuss

Yao David Logan

Software Engineer fullstack specialized in SaaS, business automation and scalable web/mobile platforms.

NavigationExpertiseProjectsJourneyBlogContact
LinksGitHubLinkedInEmail
© 2026 Yao David Logan. All rights reserved.
60 fps on entry-level Android — React Native performance playbook for emerging markets
Back to blog

Tutoriel

Xin@

60 fps on entry-level Android — React Native performance playbook for emerging markets

YDLYao David Logan17 min readSeptember 18, 2026

CategoryTutoriel

Read17 min read

PublishedSeptember 18, 2026

Views0

Shares0

Comments0

Contents
  1. 011. Why it matters
  2. 022. Measure before optimizing
  3. 033. Cold start
  4. 044. Lists — where it all plays out
  5. 055. Images — your worst enemy
  6. 066. Animations — InteractionManager + Reanimated
  7. 077. Offline-first
  8. 088. Bundle size — every kilo counts
  9. 099. Network — battle-tested patterns
  10. 1010. Spark Go verdict
  11. 1111. Closing

Hermes, FlashList, react-native-fast-image, UI-thread Reanimated, offline-first React Query: the complete guide to a stable 60 fps on a 2 GB Tecno Spark Go.

On the Nexus mobile app, 68% of users have an entry-level Android device under €100 with 2 GB of RAM and a slow 4-core CPU. Here's the playbook that took me to a stable 60 fps on those devices.

1. Why it matters

When you develop on a €1,200 iPhone Pro, you never see the real problems. Everything is smooth. But the majority of French-speaking West African users — and a substantial slice of emerging markets in general — run on entry-level Android devices: Tecno, Itel, Infinix, Xiaomi A-series. These phones have:

  • 2 GB of RAM (sometimes 3, rarely 4)
  • 4-core ARM Cortex-A53 or A55 CPU — slow
  • Mali-G52 GPU or equivalent — limited
  • Slow eMMC storage (read < 100 MB/s)
  • 3G/4G with 600+ ms latency and 500 kbps bandwidth

In these conditions, a default React Native app stutters. Not a little — a lot. Scroll frame drops, choppy transitions, 8-second cold starts. The perception: "this app is broken."

This article: what I changed on the Nexus mobile app to go from 18 fps to a stable 58 fps on a Tecno Spark Go.

2. Measure before optimizing

Without measurement, you optimize blind. Three tools mattered:

Flipper + Performance Monitor: shows UI and JS thread FPS as overlay. It's my reference. If I see the JS thread drop below 30 fps, I have a logic problem. If it's the UI thread, it's a rendering problem.

Android Studio Profiler: for cold start and memory usage. I profile from onCreate to the first interactive screen.

Reactotron: to trace re-renders. If a component re-renders 12 times during a transition, I know.

TSX
1// hooks/useRenderCount.ts — handy in dev
2import { useRef } from "react";
3 
4export function useRenderCount(name: string) {
5 const count = useRef(0);
6 count.current += 1;
7 if (__DEV__) {
8 console.log(`[render] ${name} — ${count.current}`);
9 }
10}

3. Cold start

That's the user's first glance. On Spark Go, the default app took 7.8 seconds. Target: under 3 seconds. What moved the needle:

3.1 Hermes enabled

Hermes is the JavaScript engine optimized for React Native on Android. It pre-compiles bytecode, shrinks the bundle and starts 30–50% faster. On Spark Go: -2.1 seconds.

JSON
1// android/app/build.gradle
2project.ext.react = [
3 enableHermes: true,
4]

3.2 Native splash screen

The classic JS splash waits for React to mount. Slow on entry-level. The native splash shows from Android boot, before React. Combined with react-native-bootsplash, it creates the illusion of instant startup.

3.3 Lazy load screens

The router loads all screens at init by default. Bad. With React.lazy or a router that supports native lazy loading (React Navigation 6+ with @react-navigation/native-stack):

TSX
1import { createNativeStackNavigator } from "@react-navigation/native-stack";
2import { lazy } from "react";
3 
4const HomeScreen = lazy(() => import("../screens/HomeScreen"));
5const PortfolioScreen = lazy(() => import("../screens/PortfolioScreen"));
6 
7const Stack = createNativeStackNavigator();

Initial bundle drops from 4.2 MB to 1.8 MB. Startup: -1.2 seconds.

4. Lists — where it all plays out

90% of fluidity problems come from lists. On Nexus, "My investments" displays up to 200 rows. Rules:

4.1 Always FlatList (or FlashList), never ScrollView + map

ScrollView renders all children even off-screen. FlatList virtualizes. Shopify's FlashList goes further by using native cell recycling:

TSX
1import { FlashList } from "@shopify/flash-list";
2 
3<FlashList
4 data={investments}
5 estimatedItemSize={88}
6 renderItem={({ item }) => <InvestmentCard investment={item} />}
7 keyExtractor={(item) => item.id}
8/>

4.2 getItemLayout when height is known

If each item has the same height, giving it to FlatList avoids measurement and speeds up scrolling.

4.3 React.memo on items

Without memo, every scroll re-renders all visible items, even if props haven't changed:

TSX
1export const InvestmentCard = React.memo(function InvestmentCard({ investment }: Props) {
2 return (
3 <View style={styles.card}>
4 <Text>{investment.label}</Text>
5 <Text>{investment.amount}</Text>
6 </View>
7 );
8}, (prev, next) =>
9 prev.investment.id === next.investment.id &&
10 prev.investment.amount === next.investment.amount
11);

The custom comparator skips the default shallow check that re-renders as soon as a prop changes reference.

5. Images — your worst enemy

A 4 MB image downloaded and decoded blocks the JS thread for seconds. On Spark Go, deadly. Three rules:

5.1 Serve sizes adapted to device

Ask the backend for the right size based on PixelRatio.get(). Not 1920×1080 on a 720p screen.

TSX
1import { PixelRatio } from "react-native";
2 
3const width = 360;
4const ratio = PixelRatio.get();
5const imageUrl = `${cdn}/photo.jpg?w=${width * ratio}&q=80`;

5.2 react-native-fast-image

The default Image component has weak cache and blocks decoding. react-native-fast-image uses SDWebImage (iOS) and Glide (Android) — native off-thread decoding, smart disk and memory cache.

5.3 Modern format

WebP shrinks file size by 30–40% vs JPEG and is natively supported on Android 4.0+. AVIF even more, but limited to Android 12+. Serve AVIF when the client accepts it, fallback WebP.

6. Animations — InteractionManager + Reanimated

Animating in JS on entry-level = guaranteed frame drops. The rule: every animation must run on the UI thread.

react-native-reanimated v3 lets you write "worklets" that execute on the UI thread with no round-trip to JS:

TSX
1import Animated, { useSharedValue, useAnimatedStyle, withSpring } from "react-native-reanimated";
2 
3function SwipeableRow() {
4 const offset = useSharedValue(0);
5 
6 const style = useAnimatedStyle(() => ({
7 transform: [{ translateX: offset.value }],
8 }));
9 
10 const onSwipe = (delta: number) => {
11 offset.value = withSpring(delta);
12 };
13 
14 return <Animated.View style={style} />;
15}

For heavy screen transitions (animation + data load), defer non-critical work with InteractionManager.runAfterInteractions. It lets the runtime finish the transition before fetching.

7. Offline-first

On 3G with 600 ms latency, waiting for a network request before showing anything is unbearable. Rule: always show first, refresh later.

TSX
1import { useQuery } from "@tanstack/react-query";
2 
3export function useInvestments(userId: string) {
4 return useQuery({
5 queryKey: ["investments", userId],
6 queryFn: () => api.fetchInvestments(userId),
7 staleTime: 0,
8 gcTime: 1000 * 60 * 60 * 24,
9 refetchOnMount: "always",
10 });
11}

Combined with an AsyncStorage persister for React Query, the user opens the app and sees their last data immediately, even offline. Fetch follows in background and updates.

8. Bundle size — every kilo counts

On 3G, 1 MB = 16 seconds of download. The bundle must be minimal:

  1. Audit with source-map-explorer — visualizes who weighs what. Often an 800 KB library added for a single debounce function.
  2. Strict tree-shaking — named imports (import { debounce } from "lodash-es") not whole libs.
  3. Minimal polyfills — core-js configured for Android API 21+, not Android 4.
  4. ProGuard enabled in release — shrink, obfuscate, dead-code removal.

9. Network — battle-tested patterns

PatternWhenGain
Server-side gzip compressionAlways-60% payload
Multiplexed HTTP/2Always-300 ms per cascading request
Idle pre-fetchBefore likely transitionPerceived-instant transition
Keep-alive connectionAlways-100 ms per additional request
Exponential backoffOn retryAvoids overload on network failure

10. Spark Go verdict

After applying all patterns on Nexus Mobile:

MetricBeforeAfter
Cold start7.8 s2.4 s
200-item scroll FPS1858
Idle memory280 MB145 MB
Initial bundle4.2 MB1.8 MB
Time-to-interactive9.1 s3.2 s

The highest-leverage moves were switching to FlashList + memo (+30 fps instantly) and enabling Hermes + lazy loading (-3.2 s startup). The rest is daily rigor.

11. Closing

React Native on entry-level Android isn't a framework problem, it's a discipline problem. Hermes, virtualized lists, UI-thread animations, native images, bundle audit, offline-first. None of these patterns is revolutionary, but their systematic application separates an app that works from an app users keep on their phone.

The metric to watch: time-to-interactive on a defined target device. If it's under 3 seconds on the low end of the market, your app is usable anywhere.

Comments

Reader reactions

No comment yet

No spam — email is only used to verify your identity.

·

Be the first to share your reaction.

←PreviousFlutter vs React Native in 2026 — honest comparison after two years on bothTech · 15 min readNextMulti-gateway payment and E2E encryption — the pattern that doesn't breakTech · 18 min read→
Next conversation

Turn this reading into a product decision.

If this topic feels close to a real product problem, I can help on diagnosis, architecture, backend, interface and automations that make a platform usable in production.

Format
Full-time, freelance, long mission
Focus
SaaS, API, back-office, automation
Discuss the topicDownload CV

Newsletter

Get the next technical notes.

A short selection on SaaS, backend architecture, business automation and product quality. No noise, only applicable ideas.