Branch8

React Native Performance Optimisation for APAC Low-Bandwidth Networks

Elton Chan
April 30, 2026
14 mins read
React Native Performance Optimisation for APAC Low-Bandwidth Networks - Hero Image

Key Takeaways

  • Profile on real APAC network conditions, not office Wi-Fi
  • Network-aware hooks adapt image quality and page sizes automatically
  • FlashList cuts scroll frame drops by up to 90% on budget devices
  • ABI splitting and Hermes reduce APK and bundle size by 30-46%
  • Offline-first persistence is essential for intermittent APAC connectivity

Quick Answer: Optimise React Native for APAC low-bandwidth by profiling under realistic network throttles, enabling Hermes, implementing network-aware data fetching hooks, using adaptive image loading, migrating to FlashList, adding offline-first persistence with WatermelonDB, and reducing APK size via ABI splitting.


Most React Native performance guides assume your users sit on stable 4G or Wi-Fi in San Francisco. That assumption breaks immediately across APAC, where according to Opensignal's 2024 Mobile Network Experience Report, average download speeds in the Philippines hover around 13 Mbps, Indonesia at 16 Mbps, and rural Vietnam can dip below 5 Mbps on congested 3G towers. React Native performance optimisation for APAC low-bandwidth conditions demands a fundamentally different playbook — one tuned for packet loss, high latency, and the budget Android devices that dominate Southeast Asian markets.

Related reading: Claude AI FreeBSD Kernel Vulnerability Exploit: What It Means for APAC Security Teams

Related reading: Cross-Border Returns Management for APAC E-Commerce Brands: A 7-Step Integration Guide

Related reading: Top Customer Data Platforms for APAC Retail 2026: A Buyer's Scoring Guide

Related reading: Composable Commerce vs Monolithic Platform TCO Analysis: A 3-Year APAC Model

Related reading: Data Governance Framework for APAC Retail Multi-Market Ops: A 7-Step Guide

I learned this firsthand at Lazada, where we shipped mobile experiences to millions of users across six countries on networks that would make most Western QA teams panic. At Branch8, our engineering teams in Taiwan, Vietnam, and the Philippines now build and test React Native apps under these real-world constraints daily. This tutorial distils that experience into concrete, copy-pasteable steps.

Prerequisites

Before starting, ensure you have the following:

  • React Native 0.73+ (New Architecture support recommended; Hermes enabled by default)
  • Node.js 18 LTS or later
  • Android Studio Hedgehog (2023.1.1) with a configured emulator or physical device
  • Xcode 15+ for iOS builds
  • Flipper 0.230+ for profiling (or React Native DevTools if you've migrated)
  • A network throttling tool — we use Charles Proxy 4.6 or Android's built-in Developer Options network throttling
  • Familiarity with metro.config.js, babel.config.js, and basic React Native navigation

Optional but strongly recommended: a physical budget Android device. We keep a Samsung Galaxy A04 (2GB RAM, ~USD 100) and a Realme C30 in every Branch8 office specifically for low-end testing — these represent actual devices carried by millions of APAC users.

Step 1: Profile Under Realistic APAC Network Conditions

Before optimising anything, you need accurate baselines. The biggest mistake teams make is profiling on office Wi-Fi and calling it done.

Configure Charles Proxy for APAC Throttle Profiles

Create custom throttle profiles that mirror real APAC conditions. Based on Ookla Speedtest Intelligence Q1 2024 data, here are the profiles we use at Branch8:

1{
2 "profiles": [
3 {
4 "name": "PH_Rural_3G",
5 "bandwidth_down_kbps": 2500,
6 "bandwidth_up_kbps": 800,
7 "latency_ms": 280,
8 "packet_loss_pct": 3
9 },
10 {
11 "name": "VN_Urban_4G",
12 "bandwidth_down_kbps": 12000,
13 "bandwidth_up_kbps": 4000,
14 "latency_ms": 85,
15 "packet_loss_pct": 0.5
16 },
17 {
18 "name": "ID_Suburban_Mixed",
19 "bandwidth_down_kbps": 5000,
20 "bandwidth_up_kbps": 1500,
21 "latency_ms": 180,
22 "packet_loss_pct": 2
23 },
24 {
25 "name": "TW_Urban_5G",
26 "bandwidth_down_kbps": 85000,
27 "bandwidth_up_kbps": 15000,
28 "latency_ms": 25,
29 "packet_loss_pct": 0.1
30 }
31 ]
32}

Save this as apac-throttle-profiles.json and import it into Charles Proxy via Proxy → Throttle Settings → Import.

Android Device-Level Throttling

If you're testing on a physical Android device without Charles:

1# Connect device via ADB
2adb shell settings put global captive_portal_mode 0
3
4# Simulate 3G-equivalent throttle (requires root or use Android emulator)
5# In Android Emulator, use Extended Controls > Cellular > Network Type: EDGE/3G
6# Set signal strength: Moderate

Run your app and record these baseline metrics with Flipper's performance plugin:

  • Time to Interactive (TTI) from cold start
  • JS thread frame drops during scroll
  • Network waterfall — total payload size and request count on initial load
  • Memory usage at peak and idle

Document these numbers. You'll compare after each optimisation step.

Ready to Transform Your Ecommerce Operations?

Branch8 specializes in ecommerce platform implementation and AI-powered automation solutions. Contact us today to discuss your ecommerce automation strategy.

Step 2: Slash Your JavaScript Bundle Size

Bundle size directly impacts startup time, especially on low-bandwidth networks. According to Callstack's 2024 State of React Native report, the median production bundle sits around 6-8 MB — that's a 25-second download on a Philippine rural 3G connection.

Enable Hermes and Verify It's Actually Running

Hermes should be default on RN 0.73+, but we've encountered projects where it was accidentally disabled. Verify:

1// Add this to your App.tsx to verify at runtime
2import { Platform } from 'react-native';
3
4console.log(
5 'Hermes enabled:',
6 typeof HermesInternal !== 'undefined' ? 'YES' : 'NO'
7);

If Hermes is not active, enable it explicitly:

1// android/app/build.gradle
2project.ext.react = [
3 enableHermes: true,
4 hermesCommand: "../../node_modules/react-native/sdks/hermesc/%OS-BIN%/hermesc"
5]

Analyse and Tree-Shake Your Bundle

Install react-native-bundle-visualizer to identify bloat:

1npx react-native-bundle-visualizer

This generates a treemap of your bundle. In a recent Branch8 project — a fintech app targeting Vietnam and Philippines markets — we discovered that moment.js with all locales accounted for 320KB of the bundle. Replacing it with date-fns (importing only vi and fil locales) cut that to 18KB:

1// Before: 320KB
2import moment from 'moment';
3import 'moment/locale/vi';
4
5// After: 18KB
6import { format, parseISO } from 'date-fns';
7import { vi } from 'date-fns/locale';
8
9const formatted = format(parseISO(dateString), 'dd MMM yyyy', { locale: vi });

Configure Metro for Aggressive Tree Shaking

1// metro.config.js
2const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config');
3
4const config = {
5 transformer: {
6 minifierConfig: {
7 compress: {
8 drop_console: true, // Remove console.log in production
9 drop_debugger: true,
10 passes: 3, // Multiple compression passes
11 pure_getters: true,
12 },
13 mangle: {
14 toplevel: true,
15 },
16 },
17 },
18};
19
20module.exports = mergeConfig(getDefaultConfig(__dirname), config);

Expected result: 15-30% bundle size reduction depending on your dependency graph.

Step 3: Implement Intelligent Network-Aware Data Fetching

This is where APAC-specific React Native performance optimisation under low-bandwidth conditions diverges most sharply from Western guides. You can't just "add caching" — you need network-aware strategies that adapt in real time.

Detect Network Quality and Adapt

1npm install @react-native-community/netinfo
1// hooks/useNetworkAwareConfig.ts
2import NetInfo, { NetInfoStateType } from '@react-native-community/netinfo';
3import { useEffect, useState } from 'react';
4
5interface NetworkConfig {
6 imageQuality: 'low' | 'medium' | 'high';
7 pageSize: number;
8 prefetchEnabled: boolean;
9 timeoutMs: number;
10 retryCount: number;
11}
12
13const NETWORK_CONFIGS: Record<string, NetworkConfig> = {
14 poor: {
15 imageQuality: 'low',
16 pageSize: 5,
17 prefetchEnabled: false,
18 timeoutMs: 30000,
19 retryCount: 5,
20 },
21 moderate: {
22 imageQuality: 'medium',
23 pageSize: 15,
24 prefetchEnabled: true,
25 timeoutMs: 15000,
26 retryCount: 3,
27 },
28 good: {
29 imageQuality: 'high',
30 pageSize: 25,
31 prefetchEnabled: true,
32 timeoutMs: 8000,
33 retryCount: 2,
34 },
35};
36
37export function useNetworkAwareConfig(): NetworkConfig {
38 const [config, setConfig] = useState<NetworkConfig>(NETWORK_CONFIGS.moderate);
39
40 useEffect(() => {
41 const unsubscribe = NetInfo.addEventListener((state) => {
42 if (!state.isConnected) {
43 setConfig(NETWORK_CONFIGS.poor);
44 return;
45 }
46
47 if (state.type === NetInfoStateType.cellular) {
48 const generation = state.details?.cellularGeneration;
49 if (generation === '2g' || generation === '3g') {
50 setConfig(NETWORK_CONFIGS.poor);
51 } else {
52 setConfig(NETWORK_CONFIGS.moderate);
53 }
54 } else if (state.type === NetInfoStateType.wifi) {
55 setConfig(NETWORK_CONFIGS.good);
56 }
57 });
58
59 return () => unsubscribe();
60 }, []);
61
62 return config;
63}

Wire It Into Your API Layer

1// api/client.ts
2import { useNetworkAwareConfig } from '../hooks/useNetworkAwareConfig';
3
4export function createFetchWithRetry(config: ReturnType<typeof useNetworkAwareConfig>) {
5 return async function fetchWithRetry(url: string, options: RequestInit = {}) {
6 const controller = new AbortController();
7 const timeout = setTimeout(() => controller.abort(), config.timeoutMs);
8
9 for (let attempt = 0; attempt <= config.retryCount; attempt++) {
10 try {
11 const response = await fetch(url, {
12 ...options,
13 signal: controller.signal,
14 headers: {
15 ...options.headers,
16 // Tell your API to send compressed/reduced payloads
17 'X-Network-Quality': config.imageQuality,
18 'Accept-Encoding': 'gzip, br',
19 },
20 });
21 clearTimeout(timeout);
22 return response;
23 } catch (err) {
24 if (attempt === config.retryCount) throw err;
25 // Exponential backoff with jitter
26 await new Promise((r) =>
27 setTimeout(r, Math.min(1000 * Math.pow(2, attempt) + Math.random() * 500, 10000))
28 );
29 }
30 }
31 };
32}

Ready to Transform Your Ecommerce Operations?

Branch8 specializes in ecommerce platform implementation and AI-powered automation solutions. Contact us today to discuss your ecommerce automation strategy.

Step 4: Optimise Images for Variable Bandwidth

Images account for 60-80% of mobile data transfer according to HTTP Archive's 2024 Web Almanac. In low-bandwidth APAC markets, unoptimised images are the single biggest performance killer.

Use react-native-fast-image with Quality Tiers

1npm install react-native-fast-image
1// components/AdaptiveImage.tsx
2import FastImage from 'react-native-fast-image';
3import { useNetworkAwareConfig } from '../hooks/useNetworkAwareConfig';
4
5interface AdaptiveImageProps {
6 baseUrl: string;
7 width: number;
8 height: number;
9}
10
11const QUALITY_SUFFIXES = {
12 low: '?w=320&q=40&fm=webp',
13 medium: '?w=640&q=65&fm=webp',
14 high: '?w=1080&q=80&fm=webp',
15};
16
17export function AdaptiveImage({ baseUrl, width, height }: AdaptiveImageProps) {
18 const { imageQuality } = useNetworkAwareConfig();
19
20 return (
21 <FastImage
22 style={{ width, height }}
23 source={{
24 uri: `${baseUrl}${QUALITY_SUFFIXES[imageQuality]}`,
25 priority:
26 imageQuality === 'low'
27 ? FastImage.priority.low
28 : FastImage.priority.normal,
29 cache: FastImage.cacheControl.immutable,
30 }}
31 resizeMode={FastImage.resizeMode.cover}
32 />
33 );
34}

This assumes your image CDN (Cloudinary, Imgix, or AWS CloudFront with Lambda@Edge) supports URL-based transformations. If you're serving from a basic S3 bucket — stop. Set up CloudFront with an image transformation layer first.

Precompute Placeholder Hashes

Generate BlurHash strings server-side so users see immediate low-resolution placeholders:

1// Use react-native-blurhash for instant visual feedback
2import { Blurhash } from 'react-native-blurhash';
3
4// In your image component, show blurhash while FastImage loads
5{isLoading && (
6 <Blurhash
7 blurhash={item.blurhash} // e.g. "LGF5]+Yk^6#M@-5c,1J5@[or[Q6."
8 style={{ width, height, position: 'absolute' }}
9 />
10)}

Step 5: Implement Offline-First Data Persistence

In many APAC markets, network connectivity is intermittent — think users on Manila's MRT, Ho Chi Minh City's metro, or rural Indonesian regions. Your app must function without a network.

Set Up WatermelonDB for Local-First Architecture

1npm install @nozbe/watermelondb @nozbe/with-observables
2npx pod-install # iOS only
1// database/schema.ts
2import { appSchema, tableSchema } from '@nozbe/watermelondb';
3
4export const schema = appSchema({
5 version: 1,
6 tables: [
7 tableSchema({
8 name: 'products',
9 columns: [
10 { name: 'remote_id', type: 'string', isIndexed: true },
11 { name: 'title', type: 'string' },
12 { name: 'price', type: 'number' },
13 { name: 'thumbnail_url', type: 'string' },
14 { name: 'synced_at', type: 'number' },
15 { name: 'is_dirty', type: 'boolean' }, // Track local changes
16 ],
17 }),
18 ],
19});
1// database/sync.ts
2import { synchronize } from '@nozbe/watermelondb/sync';
3import { database } from './index';
4
5export async function syncWithServer() {
6 await synchronize({
7 database,
8 pullChanges: async ({ lastPulledAt }) => {
9 const response = await fetch(
10 `https://api.yourapp.com/sync?last_pulled_at=${lastPulledAt || 0}`
11 );
12 if (!response.ok) throw new Error('Sync pull failed');
13 const { changes, timestamp } = await response.json();
14 return { changes, timestamp };
15 },
16 pushChanges: async ({ changes }) => {
17 await fetch('https://api.yourapp.com/sync', {
18 method: 'POST',
19 headers: { 'Content-Type': 'application/json' },
20 body: JSON.stringify(changes),
21 });
22 },
23 migrationsEnabledAtVersion: 1,
24 });
25}

We chose WatermelonDB over AsyncStorage or MMKV for structured data because it handles sync conflicts gracefully — critical when users in the Philippines might make offline edits that need to merge once they reconnect. For simple key-value caching, MMKV remains faster (according to Callstack's benchmarks, MMKV reads are ~30x faster than AsyncStorage).

Ready to Transform Your Ecommerce Operations?

Branch8 specializes in ecommerce platform implementation and AI-powered automation solutions. Contact us today to discuss your ecommerce automation strategy.

Step 6: Optimise List Rendering for Low-End Devices

Budget Android devices — the Samsung Galaxy A-series, Xiaomi Redmi, Realme C-series — make up the majority of smartphones across Southeast Asia according to Counterpoint Research Q4 2023 data. These devices have 2-4GB RAM and slower CPUs.

Replace FlatList with FlashList

1npm install @shopify/flash-list
1// screens/ProductList.tsx
2import { FlashList } from '@shopify/flash-list';
3import { AdaptiveImage } from '../components/AdaptiveImage';
4
5export function ProductList({ products }) {
6 const { pageSize } = useNetworkAwareConfig();
7
8 return (
9 <FlashList
10 data={products}
11 estimatedItemSize={120}
12 renderItem={({ item }) => (
13 <ProductCard item={item} />
14 )}
15 // Critical for low-RAM devices
16 drawDistance={250} // Reduce from default 250 on poor networks
17 overrideItemLayout={(layout, item) => {
18 layout.size = 120; // Fixed height prevents recalculation
19 }}
20 // Maintain recycling pool for scroll performance
21 estimatedFirstItemOffset={0}
22 />
23 );
24}

Shopify's own benchmarks show FlashList achieves up to 5x fewer blank cells during fast scrolls compared to FlatList. On our Samsung Galaxy A04 test device, switching from FlatList to FlashList dropped frame drops during scroll from ~18 per second to ~3.

Avoid Anonymous Functions and Object Literals in renderItem

This is React Native 101, but it's the number-one performance issue we see in code reviews at Branch8:

1// BAD: Creates new object and function reference every render
2<FlashList
3 renderItem={({ item }) => (
4 <View style={{ padding: 10, margin: 5 }}>
5 <Text onPress={() => navigate(item.id)}>{item.title}</Text>
6 </View>
7 )}
8/>
9
10// GOOD: Memoised component with stable style references
11const styles = StyleSheet.create({
12 card: { padding: 10, margin: 5 },
13});
14
15const ProductCard = React.memo(({ item, onPress }) => (
16 <View style={styles.card}>
17 <Text onPress={onPress}>{item.title}</Text>
18 </View>
19));
20
21// In parent:
22const handlePress = useCallback((id) => navigate(id), [navigate]);

Step 7: Reduce APK Size for Faster Downloads

Google Play data from 2023 indicates that every 6MB increase in APK size reduces install conversion by 1% in emerging markets. When your users are on prepaid data plans in Indonesia or the Philippines, every megabyte costs them money.

Enable Proguard and Split ABIs

1// android/app/build.gradle
2android {
3 buildTypes {
4 release {
5 minifyEnabled true
6 shrinkResources true
7 proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
8 }
9 }
10
11 splits {
12 abi {
13 enable true
14 reset()
15 include 'armeabi-v7a', 'arm64-v8a'
16 universalApk false // Don't generate a universal APK
17 }
18 }
19}

This alone can cut APK size by 30-40%. In our fintech project, the universal APK was 42MB; after ABI splitting, the arm64-v8a variant dropped to 24MB.

Use App Bundle Instead of APK

1cd android && ./gradlew bundleRelease

Google's Android App Bundle format with Play Delivery typically delivers 15% smaller downloads than optimised APKs according to Android Developers documentation.

Ready to Transform Your Ecommerce Operations?

Branch8 specializes in ecommerce platform implementation and AI-powered automation solutions. Contact us today to discuss your ecommerce automation strategy.

A Branch8 Field Report: Vietnam Fintech App

When our Taipei and Ho Chi Minh City teams optimised a fintech app targeting Vietnamese users in Q3 2024, we applied every step in this guide over a six-week sprint. The starting metrics (tested on a Redmi 9A over Vietnamese 3G):

  • Cold start TTI: 8.4 seconds
  • Initial payload: 3.2 MB
  • JS bundle: 7.1 MB
  • Scroll frame drops: 22/sec on product list

After optimisation:

  • Cold start TTI: 2.9 seconds (65% improvement)
  • Initial payload: 480 KB (85% reduction via adaptive loading + gzip)
  • JS bundle: 3.8 MB (46% reduction)
  • Scroll frame drops: 2/sec (91% improvement)

The total engineering effort was approximately 180 person-hours across three developers — roughly USD 9,000 at Vietnamese senior developer rates. The ROI showed within four weeks: the client reported a 23% increase in daily active users and a 34% reduction in uninstalls, which they attributed directly to performance improvements in low-bandwidth regions.

Testing Your Optimisations Across APAC Markets

Don't trust emulator-only testing. Here's our testing matrix:

  • Philippines/Indonesia tier: Samsung Galaxy A04 or Redmi 9A (2GB RAM)
  • Vietnam/Thailand tier: Samsung Galaxy A14 or Redmi Note 12 (4GB RAM)
  • Taiwan/Singapore tier: Any mid-range device (6-8GB RAM)
  • iOS: iPhone SE 3rd gen (baseline) and iPhone 12 (standard)

Automated Performance Regression with Maestro

1# .maestro/performance-check.yaml
2appId: com.yourapp.mobile
3---
4- launchApp
5- assertVisible: "Home Screen"
6- scroll:
7 direction: DOWN
8 duration: 3000
9- assertVisible: "Product Card" # Verify content loaded
10- stopApp
1# Run on CI with network throttle
2maestro test .maestro/performance-check.yaml --device emulator-5554

Integrate this into your CI/CD pipeline. At Branch8, we gate releases on performance budgets — if TTI exceeds 3.5 seconds on the budget Android profile, the build fails.

Ready to Transform Your Ecommerce Operations?

Branch8 specializes in ecommerce platform implementation and AI-powered automation solutions. Contact us today to discuss your ecommerce automation strategy.

What to Do Next

React Native performance optimisation for APAC low-bandwidth conditions is not a one-time task. Networks evolve, devices change, and your app's feature set grows. Here's your immediate action plan:

  • This week: Set up the APAC throttle profiles from Step 1 and baseline your current metrics
  • Next sprint: Implement the network-aware config hook (Step 3) and adaptive image loading (Step 4) — these deliver the highest ROI for the least effort
  • This quarter: Migrate to FlashList, implement offline persistence, and establish performance budgets in CI

A trade-off to acknowledge honestly: these optimisations add complexity. The network-aware hooks, offline sync logic, and adaptive image loading increase your codebase size and testing surface. If your users are exclusively on stable broadband in Singapore, Sydney, or Taipei — and your analytics confirm this — you probably don't need the full stack described here. Start with bundle size reduction and FlashList, and layer on network adaptation only when your user data justifies it.

But if you're targeting the 400+ million smartphone users across Southeast Asia who regularly deal with patchy 3G connections and sub-USD-200 devices, this isn't optional. It's the difference between an app that gets uninstalled in frustration and one that earns daily active usage.

If your team needs help implementing these patterns — or if you need experienced React Native engineers who've already shipped under these conditions — get in touch with Branch8's engineering team.

Further Reading

FAQ

Enable Hermes, split APK by ABI architecture, use Android App Bundles, and run react-native-bundle-visualizer to identify and remove bloated dependencies. These steps typically reduce bundle size by 30-46%. Also strip console logs and enable Proguard's shrinkResources in your Gradle config.

About the Author

Elton Chan

Co-Founder, Second Talent & Branch8

Elton Chan is Co-Founder of Second Talent, a global tech hiring platform connecting companies with top-tier tech talent across Asia, ranked #1 in Global Hiring on G2 with a network of over 100,000 pre-vetted developers. He is also Co-Founder of Branch8, a Y Combinator-backed (S15) e-commerce technology firm headquartered in Hong Kong. With 14 years of experience spanning management consulting at Accenture (Dublin), cross-border e-commerce at Lazada Group (Singapore) under Rocket Internet, and enterprise platform delivery at Branch8, Elton brings a rare blend of strategy, technology, and operations expertise. He served as Founding Chairman of the Hong Kong E-Commerce Business Association (HKEBA), driving digital commerce education and cross-border collaboration across Asia. His work bridges technology, talent, and business strategy to help companies scale in an increasingly remote and digital world.