An exhaustive, production-grade masterclass from absolute zero to staff-level React Native and Expo mobile architecture.
Master the New Architecture (Fabric & TurboModules), Bridgeless execution, Hermes ahead-of-time bytecode compilation, Expo Router file-system navigation, Reanimated 3 UI-thread worklets, MMKV storage, and production iOS/Android deployments.
Getting Started • Core Primitives • Expo Router • Gestures & Animations • Device Hardware • Performance & EAS • Staff Handbook
flowchart LR
S1["Stage 1: New Architecture & Hermes"] --> S2["Stage 2: Core Primitives & Yoga Flexbox"]
S2 --> S3["Stage 3: Expo Router Navigation"]
S3 --> S4["Stage 4: Gestures & Reanimated 3"]
S4 --> S5["Stage 5: Hardware & MMKV Storage"]
S5 --> S6["Stage 6: Profiling, Offline & EAS"]
S6 --> S7["Stage 7: Staff Architect Handbook & Cheatsheet"]
| Stage | Domain Focus | Core Concepts & Production Outcomes |
|---|---|---|
| Stage 1 | New Architecture & Foundations | The death of the asynchronous Bridge, JSI (JavaScript Interface), C++ core, Fabric rendering engine, TurboModules, Hermes AOT compilation. |
| Stage 2 | Core Primitives & Yoga Layout | Yoga C++ Flexbox engine, <View>, <Text>, <Image>, <ScrollView>, virtualized lists (<FlatList>, @shopify/flash-list), Safe Area handling. |
| Stage 3 | Expo Router Navigation | Next.js-style file-based routing for native iOS/Android, Stack, Tabs, Drawer, modal interceptors, deep linking, auth protected route groups. |
| Stage 4 | Gestures & 60fps Animations | React Native Gesture Handler, Reanimated 3 worklets running on the UI thread, pan gestures, layout animations, shared element transitions. |
| Stage 5 | Hardware & High-Speed Storage | Camera, Geolocation, Biometrics (FaceID/Fingerprint), High-throughput synchronous storage with react-native-mmkv vs SQLite. |
| Stage 6 | Performance, Offline-First & EAS | Hermes trace profiling, JS thread vs UI thread frame drops, WatermelonDB/SQLite offline sync, EAS Build & OTA updates (EAS Update). |
| Stage 7 | Staff Architect Interview Handbook | 40+ staff-level scenario Q&As, Bridge vs Bridgeless benchmarks, memory leak isolation, multi-platform monorepos, enterprise cheatsheet. |
- 1. Stage 1: Foundations & The New Architecture
- 2. Stage 2: Core Components & Yoga Layout Engine
- 3. Stage 3: File-Based Navigation with Expo Router
- 4. Stage 4: 60fps Gestures & Reanimated 3 Animations
- 5. Stage 5: Native Device Hardware & High-Speed Storage
- 6. Stage 6: Enterprise Performance, Profiling & EAS
- 7. Stage 7: Staff Mobile Architect Interview Handbook & Cheatsheet
- 🤝 Contributing
- 📄 License
In the legacy React Native architecture, JavaScript and Native platforms communicated across an asynchronous Bridge:
graph TD
subgraph OldBridge ["Legacy Architecture (The Asynchronous Bridge)"]
JS1["JavaScript Thread"] -->|1. Serialize to JSON String| Bridge["Async Serialized JSON Bridge"]
Bridge -->|2. Queue & Send over Socket| Native1["Native Thread (iOS / Android)"]
Native1 -->|3. Deserialize JSON & Update UI| FrameDrop["Frame Drops on Heavy Scrolling!"]
end
subgraph NewArch ["The New Architecture (JSI / Bridgeless Mode)"]
JS2["JavaScript (Hermes Engine)"] -->|Direct C++ Memory Pointer Invocation (JSI)| HostObjects["Native Host Objects (C++)"]
HostObjects -->|Synchronous, Zero-Copy Execution| Native2["Fabric Renderer & TurboModules"]
end
The old bridge suffered from 3 fatal flaws:
- Asynchronous Serialization Overhead: Every string, layout coordinate, and touch event had to be converted to JSON strings and parsed on both ends.
- No Synchronous Execution: JavaScript could not synchronously inspect native measurements or trigger native operations without a roundtrip delay.
- Queue Congestion: Fast scrolling resulted in white blank boxes because layout updates queued behind heavy JavaScript computations.
- JSI (JavaScript Interface): A lightweight, general-purpose C++ API that allows JavaScript code to directly hold references to C++ Host Objects and call methods synchronously without JSON serialization.
- Fabric: The modern rendering engine built entirely in C++. It creates an immutable C++ shadow tree and coordinates layout directly with Yoga on background threads.
- TurboModules: Native modules that load lazily on demand. In the legacy architecture, all native modules were instantiated on app boot, dragging down cold startup performance.
Hermes is an open-source JavaScript engine developed by Meta, specifically optimized for mobile applications:
- AOT (Ahead-of-Time) Compilation: Instead of parsing JavaScript source code at runtime, Hermes compiles JavaScript into bytecode (
.hbc) at build time. - Instant Cold Starts: The mobile OS loads precompiled bytecode directly into memory via memory-mapped files (
mmap), reducing Time-to-Interactive (TTI) by up to 50%.
Unlike the web where flex-direction defaults to row, React Native uses the Yoga Flexbox engine, where:
flexDirectiondefaults to'column'.- Dimensions are density-independent pixels (
dpon Android,pointson iOS), never percentages unless specified as strings ('100%').
import React from 'react';
import { View, Text, StyleSheet, Pressable } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
export function DashboardHeader() {
return (
<SafeAreaView edges={['top']} style={styles.safeContainer}>
<View style={styles.headerRow}>
<View>
<Text style={styles.greetingText}>Welcome back,</Text>
<Text style={styles.userNameText}>Alex Morgan</Text>
</View>
<Pressable
style={({ pressed }) => [styles.avatarButton, pressed && styles.avatarPressed]}
onPress={() => console.log('Profile opened')}
>
<Text style={styles.avatarInitials}>AM</Text>
</Pressable>
</View>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
safeContainer: {
backgroundColor: '#0F172A',
},
headerRow: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingHorizontal: 20,
paddingVertical: 16,
},
greetingText: {
fontSize: 14,
color: '#94A3B8',
},
userNameText: {
fontSize: 22,
fontWeight: '700',
color: '#F8FAFC',
},
avatarButton: {
width: 44,
height: 44,
borderRadius: 22,
backgroundColor: '#3B82F6',
justifyContent: 'center',
alignItems: 'center',
},
avatarPressed: {
opacity: 0.8,
transform: [{ scale: 0.96 }],
},
avatarInitials: {
color: '#FFFFFF',
fontWeight: '600',
fontSize: 16,
},
});For lists with hundreds or thousands of items, standard <ScrollView> causes out-of-memory crashes because it renders all items simultaneously. Use @shopify/flash-list, which recycles views instead of unmounting them:
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
import { FlashList } from '@shopify/flash-list';
interface Transaction {
id: string;
merchant: string;
amount: number;
}
export function TransactionHistory({ transactions }: { transactions: Transaction[] }) {
return (
<View style={styles.container}>
<FlashList
data={transactions}
estimatedItemSize={72}
keyExtractor={(item) => item.id}
renderItem={({ item }) => (
<View style={styles.row}>
<Text style={styles.merchant}>{item.merchant}</Text>
<Text style={styles.amount}>${item.amount.toFixed(2)}</Text>
</View>
)}
/>
</View>
);
}
const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: '#0F172A' },
row: {
height: 72,
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingHorizontal: 16,
borderBottomWidth: 1,
borderBottomColor: '#1E293B',
},
merchant: { color: '#F8FAFC', fontSize: 16, fontWeight: '500' },
amount: { color: '#10B981', fontSize: 16, fontWeight: '700' },
});Expo Router leverages directory hierarchy to declare native iOS and Android navigation:
app/
├── _layout.tsx # Root Provider & Root Stack Navigator
├── (auth)/ # Route group: Unauthenticated stack
│ ├── login.tsx # /login
│ └── register.tsx # /register
├── (tabs)/ # Route group: Tab Navigator
│ ├── _layout.tsx # Tab configuration
│ ├── index.tsx # Tab 1: Home
│ └── settings.tsx # Tab 2: Settings
└── modal.tsx # Presentation modal sheet
// app/(tabs)/_layout.tsx
import { Tabs } from 'expo-router';
import { Ionicons } from '@expo/vector-icons';
export default function TabLayout() {
return (
<Tabs
screenOptions={{
headerShown: true,
tabBarStyle: { backgroundColor: '#0F172A', borderTopColor: '#1E293B' },
tabBarActiveTintColor: '#38BDF8',
tabBarInactiveTintColor: '#64748B',
}}
>
<Tabs.Screen
name="index"
options={{
title: 'Home',
tabBarIcon: ({ color, size }) => <Ionicons name="home" color={color} size={size} />,
}}
/>
<Tabs.Screen
name="settings"
options={{
title: 'Settings',
tabBarIcon: ({ color, size }) => <Ionicons name="settings" color={color} size={size} />,
}}
/>
</Tabs>
);
}Standard JavaScript animations drop frames if the JavaScript thread is busy parsing JSON or calculating business logic. Reanimated 3 executes animation functions (Worklets) directly on the native UI thread at 60fps / 120fps (ProMotion):
import React from 'react';
import { StyleSheet, View } from 'react-native';
import { GestureDetector, Gesture } from 'react-native-gesture-handler';
import Animated, { useSharedValue, useAnimatedStyle, withSpring } from 'react-native-reanimated';
export function DraggableCard() {
const translateX = useSharedValue(0);
const translateY = useSharedValue(0);
const panGesture = Gesture.Pan()
.onUpdate((event) => {
'worklet'; // Runs on the native UI thread!
translateX.value = event.translationX;
translateY.value = event.translationY;
})
.onEnd(() => {
'worklet';
// Smooth native spring back to initial position
translateX.value = withSpring(0, { damping: 15 });
translateY.value = withSpring(0, { damping: 15 });
});
const animatedStyle = useAnimatedStyle(() => ({
transform: [
{ translateX: translateX.value },
{ translateY: translateY.value },
],
}));
return (
<View style={styles.container}>
<GestureDetector gesture={panGesture}>
<Animated.View style={[styles.card, animatedStyle]} />
</GestureDetector>
</View>
);
}
const styles = StyleSheet.create({
container: { flex: 1, justifyContent: 'center', alignItems: 'center' },
card: { width: 140, height: 140, borderRadius: 20, backgroundColor: '#8B5CF6' },
});AsyncStorage is slow and asynchronous. react-native-mmkv connects directly to Tencent's MMKV C++ engine via JSI, reading and writing synchronously with zero serialization overhead:
import { MMKV } from 'react-native-mmkv';
export const storage = new MMKV({
id: 'enterprise-app-storage',
encryptionKey: 'SUPER_SECRET_MMKV_ENCRYPTION_KEY',
});
// Synchronous read and write (100x faster than AsyncStorage!)
export function saveUserAuth(token: string, userId: string) {
storage.set('auth_token', token);
storage.set('user_id', userId);
}
export function getAuthToken(): string | undefined {
return storage.getString('auth_token');
}Production deployment pipeline using Expo Application Services (EAS):
// eas.json
{
"cli": {
"version": ">= 12.0.0"
},
"build": {
"development": {
"developmentClient": true,
"distribution": "internal"
},
"preview": {
"distribution": "internal",
"channel": "preview"
},
"production": {
"channel": "production",
"autoIncrement": true
}
}
}Deploying instant bugfixes without waiting for Apple App Store or Google Play review:
# Publish instant OTA update to production channel
eas update --channel production --message "Fix critical checkout rounding error"| Component / Library | Purpose | Recommended Modern Tool |
|---|---|---|
| JS Engine | Bytecode Ahead-of-Time Engine | Hermes (Default in RN 0.70+) |
| Layout Engine | Cross-Platform C++ Flexbox | Yoga Engine |
| Virtual Lists | Cell recycling virtualization | @shopify/flash-list |
| Key-Value Storage | Synchronous JSI storage | react-native-mmkv |
| Animations | Native UI thread worklets | react-native-reanimated v3 |
| Routing | File-system native navigation | Expo Router v4 |
| Builds & CI/CD | Cloud builds & OTA hotfixes | Expo Application Services (EAS) |
Q1: What was the root cause of the "white blank screen" during fast scrolling in legacy React Native, and how does the New Architecture fix it?
Answer: In the legacy architecture, scroll events were emitted from the native platform, serialized into JSON, queued across the asynchronous Bridge, received by JavaScript, calculated by layout, serialized into new JSON instructions, and queued back to the native thread. On fast scrolls, the JavaScript thread could not keep up with the 60/120Hz display refresh rate, leaving the native view queue empty of rendered cells. With the New Architecture (Fabric), layout is performed synchronously on C++ background threads via JSI with direct zero-copy memory access, eliminating frame drops.
Q2: How does JSI (JavaScript Interface) allow C++ native code to be called synchronously from JavaScript?
Answer: JSI is an abstraction layer that exposes the JavaScript engine's internal runtime to C++. It allows C++ classes to be registered as
HostObjectsdirectly into the JavaScript global context. When JavaScript accesses a method on a HostObject, the engine directly invokes the underlying C++ function pointer in the same memory space, completely bypassing JSON serialization.
Answer:
AsyncStorageperforms asynchronous SQLite or file-system reads across serialized asynchronous boundaries. Reading an auth token or theme requires async callbacks and causes layout flashing on initial app boot. In contrast,react-native-mmkvuses memory-mapped files (mmap) via JSI, enabling synchronous microsecond reads directly on app startup before the initial view paints.
Answer: Flutter bundles its own complete rendering engine (Skia or Impeller) and draws every pixel on a blank native canvas, completely bypassing native OS UI widgets (UIKit/Android Views). React Native leverages the native operating system's genuine UI widgets (
UIView,android.view.View), resulting in 100% native accessibility, OS-level text selection, genuine platform look-and-feel, and seamless integration with existing native Swift/Kotlin libraries.
(36 additional Staff-level architecture scenarios detailed inside repository documentation...)
Contributions are welcomed! Fork the repository, create a feature branch, and submit a PR conforming to the unified curriculum architecture.
Released under the MIT License.