How Instagram, WhatsApp, Uber & Netflix Would Be Built Today Using Expo Router
Building a todo app is easy. Building Instagram is hard.
The difference isn't the code. It's the architecture.
Let's look at how massive mobile apps scale using React Native and Expo Router. No fluff. Just engineering.
Why Simple Folders Fail at Scale
Beginners group files by type.
/screens/components/hooks
This works for 10 screens. It breaks at 100.
When fixing a bug in the "Chat" feature, you jump between five different folders. Context switching kills productivity.
Production thinking groups by feature.
Feature-Based Folder Architecture
Big apps isolate features. A feature owns its screens, components, and logic.
/src
/app # Expo Router (file-based routing)
/(auth) # Auth group (login, signup)
/(tabs) # Main app tabs
/features
/feed # Instagram feed logic
/chat # WhatsApp messaging logic
/map # Uber map logic
/video # Netflix player logic
/core
/api # Network layer
/storage # Local DB, async storage
/ui # Dumb, reusable components (Button, Text)
Expo Router handles the URLs. The features folder handles the business logic.
Scaling the Giants
Let's break down how specific apps solve hard problems.
Instagram: Feeds & Shared Layouts
The Challenge: Endless scrolling, heavy media, nested navigation.
The Fix: Shared layouts and nested routing. Expo Router lets you wrap routes in layouts. The bottom tab bar stays mounted while you navigate deep into a profile.
Media Caching: Don't re-download images. Use
expo-imagewith aggressive disk caching.List Virtualization: Never use
ScrollViewfor feeds. UseFlashList. It only renders what's on screen.
WhatsApp: Realtime & Offline-First
The Challenge: Messages must work without internet and sync instantly when back online.
The Fix: Local-first architecture.
User sends message.
App saves to local database (WatermelonDB or SQLite) immediately. UI updates instantly.
Background worker pushes to server via WebSockets.
Server confirms. UI updates status to "Delivered".
If offline, messages queue locally. Network returns? Sync automatically.
Uber: Maps & Live Location
The Challenge: Maps eat RAM. Live tracking drains battery. UI must stay smooth at 60fps.
The Fix: Isolate heavy native code.
Throttling: Don't update React state on every GPS ping. Throttle updates to 1 per second.
Native Modules: Run complex routing math on the native thread (Swift/Kotlin), not the JavaScript thread.
Map Clustering: Group 1,000 nearby cars into 1 icon.
Netflix: Heavy Content & Startup
The Challenge: App must open in < 1 second. Video players are heavy.
The Fix: Startup optimization.
Defer Loading: Don't load the video player code on app launch. Load it only when the user clicks "Play".
Prefetching: Fetch the first row of movie posters while the splash screen is visible.
Skeleton UI: Show gray boxes immediately. Fill with data later. Perceived speed > actual speed.
Core Systems at Scale
Authentication Flow
Don't check auth on every screen. Do it at the root.
// app/_layout.tsx
export default function RootLayout() {
const { user, isLoading } = useAuth();
if (isLoading) return <SplashScreen />;
// Expo Router handles the redirect automatically
return user ? <SignedInStack /> : <SignedOutStack />;
}
API & State Management
Stop putting server data in Redux or Zustand.
Server State: Use React Query (TanStack Query). It handles caching, retries, and background refetching.
Client State: Use Zustand. Only for UI state (is modal open? which tab is active?).
[Component] --> [React Query] --> [API Wrapper] --> [Server]
|
+---> [Zustand Store] (UI State only)
App Startup Lifecycle
Every millisecond counts.
Native Init: iOS/Android boots the app.
JS Bundle: Metro loads the JS. (Keep bundle small!)
Hydration: React mounts the root component.
Data Fetch: API calls fire.
Optimization: Use Hermes engine. Enable RAM bundles (inline requires). Delay non-critical initializations (like analytics) until after the first paint.
Tradeoffs at Scale
There is no perfect architecture. Only tradeoffs.
Local-first (WhatsApp): Great UX. Hard to write. Sync conflicts are painful.
Modularization: Speeds up build times. Makes sharing code between features annoying.
Expo Router: Amazing developer experience. Sometimes limits ultra-custom native navigation transitions.
Teams pick the poison that hurts the least for their specific product.
Quick Takeaway
Stop grouping by file type. Group by feature.
Use Expo Router layouts. Keep tabs and headers mounted.
Separate server state and client state. React Query for API, Zustand for UI.
Think offline-first. The network will fail. Plan for it.
Optimize the first second. Defer heavy code. Fake the speed with skeleton screens.
Build for the app you want to have in two years, not the app you have today.

