DEV Community

Cover image for How Instagram, WhatsApp, Uber & Netflix Would Be Built Today Using Expo Router
Swyom Sanjog
Swyom Sanjog

Posted on

How Instagram, WhatsApp, Uber & Netflix Would Be Built Today Using Expo Router

When we open apps like Instagram, WhatsApp, Uber, or Netflix, it all feels simple. Tap, scroll, swipe — everything just works.But behind that smooth experience… there’s a very carefully designed architecture.If we were to build these apps today using React Native + Expo Router, the approach would be very different from older methods.

This blog will walk you through how modern apps are actually structured — in a way that you can understand and apply.

1.How Modern Large-Scale Mobile Apps Are Structured

Big apps are not built screen-by-screen.They are built like systems, not just UI.

Think of it like this:

  • Small app → one folder, few screens
  • Large app → multiple systems working together:
  • UI
  • Navigation
  • State
  • API layer
  • Realtime engine
  • Cache layer

Modern apps follow:

  • Separation of concerns
  • Feature-based structure
  • Scalable navigation
  • Centralized state management

2.Why Architecture Matters in React Native Applications

Think of building an app like organizing a massive, busy hospital. If you don't have a solid architectural blueprint before laying bricks, you end up with chaos: turning on the X-ray machine might accidentally cut the power in the operating room. In React Native, bad architecture forces the JavaScript logic and the phone's hardware to constantly scream at each other across a narrow communication bridge. This traffic jam causes your app to freeze, scroll terribly, and lag.

Furthermore, without clean structure, your code gets tangled together like spaghetti. Changing a single button color in a chat screen could accidentally break the camera or crash the video player because everything is secretly connected. Poor layout also forces the app to secretly refresh hidden pages in the background, making the user's phone get hot and draining their battery instantly. Good architecture acts as the invisible skeleton that keeps features safely isolated, stops battery drain, and ensures the app runs lightning-fast even as it grows.

If you ignore architecture:

  • Your app becomes messy
  • Bugs increase
  • Performance drops
  • Hard to scale features

If you follow good architecture:

  • Easy to add features
  • Easy to debug
  • Smooth performance
  • Team collaboration becomes easier

👉 In real companies, architecture matters more than UI.

3.Folder Architecture Using Expo Router

Expo Router uses file-based routing, similar to Next.js.

app/
│
├── (auth)/
│   ├── login.tsx
│   ├── register.tsx
│
├── (tabs)/
│   ├── home/
│   │   ├── index.tsx
│   │   ├── post.tsx
│   │
│   ├── search/
│   ├── profile/
│
├── _layout.tsx
├── index.tsx

features/
│
├── auth/
├── chat/
├── feed/
├── ride/
├── video/

services/
│
├── api.ts
├── socket.ts

store/
│
├── userStore.ts
├── appStore.ts

components/
utils/
hooks/

Enter fullscreen mode Exit fullscreen mode

4.Feature-Based Separation in Large Applications

Instead of grouping by type (components, screens), modern apps group by features.

Example:

features/chat/
  ├── ChatScreen.tsx
  ├── ChatService.ts
  ├── chatStore.ts
Enter fullscreen mode Exit fullscreen mode

👉 Why this works:

  • Everything related to chat is in one place
  • Easy to maintain
  • Easy to scale

5.Navigation Architecture for Scalable Apps

Expo Router gives you a file based routing system, In which your files are treated as routes, amazing right?

Expo Router's file-based navigation scales elegantly because each folder level adds exactly one layer of UI. The root _layout.tsx wraps everything. The tabs layout adds the tab bar. Each tab's layout adds a stack navigator. The result is a navigation hierarchy that mirrors your product hierarchy.

6.Authentication Flow Architecture

Expo Router makes auth guards natural. In the root _layout.tsx, you check auth state and render either the (auth) group or the (tabs) group. The route groups in parentheses don't affect URLs — they just let you share layouts.

     App Start
         ↓
    Check Token
         ↓
   Is Logged In?
   ↓          ↓
  Yes         No
  ↓           ↓
Main App    Auth Screens
Enter fullscreen mode Exit fullscreen mode
app/
│
├── _layout.tsx          ← Root layout (auth check here)
├── index.tsx            ← Splash / loader
│
├── (auth)/
│   ├── login.tsx
│   ├── signup.tsx
│
├── (tabs)/
│   ├── _layout.tsx
│   ├── home.tsx
│   ├── profile.tsx
│
store/
│   └── authStore.ts

Enter fullscreen mode Exit fullscreen mode

7.State management strategies for large apps

The biggest mistake I made early on: putting everything in one state manager. Server data (API responses) and client state (selected tab, modal open) have completely different lifecycles. Mixing them creates needless complexity.

The pattern that works at scale:

  • Zustand:- Global UI state, auth tokens, user preferences, non-server data.
  • React Query:- All server data — with caching, background refetch, stale-while-revalidate.
  • SQLite:- Offline persistence, message history,downloaded content metadata.
  • Context:- Truly tree-local state only — theme provider, modal state for a single screen.

8.API handling and networking layers

Every API call in a large app goes through a single centralized client — never raw fetch in a component. This buys you auth token injection, automatic refresh on 401, timeout handling, and base URL configuration — all in one place.

// lib/api-client.ts
import axios from 'axios';
import { useAuthStore } from '../store/authStore';

const api = axios.create({
  baseURL: process.env.EXPO_PUBLIC_API_URL,
});

// Attach token
api.interceptors.request.use((config) => {
  const token = useAuthStore.getState().token;
  if (token) config.headers.Authorization = `Bearer ${token}`;
  return config;
});

// Handle 401
api.interceptors.response.use(
  (res) => res,
  async (err) => {
    if (err.response?.status === 401) {
      useAuthStore.getState().logout();
    }
    return Promise.reject(err);
  }
);

export default api;
Enter fullscreen mode Exit fullscreen mode

9.Realtime Systems

Real-time features make an app feel alive, but different features require completely different tech stacks behind the scenes.

  1. Chat Systems (Like WhatsApp): To pass text back and forth instantly, apps use WebSockets. Unlike a standard web request where your phone asks a server for data and hangs up, a WebSocket keeps an open, continuous "phone call" running between your app and the server. The moment a message drops, it slips through this open line instantly.

  2. Live Updates (Like Instagram Likes):If millions of people are watching a live stream, WebSockets can become too heavy for the server. Instead, engineers use Server-Sent Events (SSE). This is a one-way street where the server safely pushes updates (like new view counts or likes) to your phone without your phone needing to talk back.

3.Ride Tracking (Like Uber): Uber uses continuous geospatial telemetry. Your driver’s phone constantly streams GPS coordinates to a central hub, which instantly calculates distances using math formulas—like the Haversine or Euclidean distance formulas:

10.Offline-first support and caching

Offline-first means your app works without internet and syncs when connectivity returns. It's not optional for apps with global users — trains, elevators, rural areas happen.

The mental model: write locally first, show immediately, sync in the background. A message in WhatsApp shows as "sending" the instant you tap send — it was written to SQLite locally. The server sync is background work.

11.App startup optimization

Large apps are slow to start — unless you're deliberate. The three biggest wins:

  1. Keep the critical path minimal. On startup, only load what's needed to determine the auth state and show the first screen. Everything else is lazy.

  2. Hold the splash screen. Use expo-splash-screen to keep the native splash visible while you do async setup (load tokens, prefetch user data). Only hide it when you're ready. Never show a blank white screen.

  3. Lazy-load heavy modules. The map SDK, video player, and camera don't need to be in your initial bundle. Load them when the user navigates to those screens.

12.Performance considerations in production

Beyond startup, production apps fight a constant battle against jank. The key culprits and their fixes:

Long lists. Never use a standard ScrollView for lists that could have 100+ items. Use FlashList (from Shopify) — it recycles cells like UITableView and is dramatically faster than FlatList.

Image loading. Use expo-image, not the built-in Image component. It supports caching, priority loading, and progressive blur-up by default.

Heavy computation on the main thread. Anything that takes more than a few milliseconds (sorting, filtering large lists, cryptography) should run in a worklet via react-native-reanimated or a background thread via expo-task-manager.

Re-renders. Profile with React DevTools before optimizing. Most re-render problems come from missing useCallback/useMemo on props passed to deeply nested components — but don't add them everywhere speculatively.

13.Shared layouts and nested routing in Expo Router

Each _layout.tsx file wraps all routes at its level. This means the tab bar (defined in (tabs)/_layout.tsx) stays visible as you navigate between tabs. A custom header defined in feed/_layout.tsx wraps only the feed screens. Nothing leaks between feature boundaries.

{`// app/(tabs)/_layout.tsx — tab bar lives here
export default function TabLayout() {
  return (
    <Tabs>
      <Tabs.Screen name="feed" options={{ title: "Feed", tabBarIcon: ... }} />
      <Tabs.Screen name="messages" options={{ title: "Chats" }} />
      <Tabs.Screen name="map" options={{ title: "Map" }} />
    </Tabs>
  );
}`}
Enter fullscreen mode Exit fullscreen mode

When navigating from feed/index to feed/[postId], the tab bar stays because it's defined above the feed stack in the hierarchy. The post screen only adds a back button — via feed/_layout.tsx — and doesn't need to know anything about the tab bar.

14.Scalability Challenges Across Tech Giants

Every major app faces a unique technical monster. Here is what engineering teams are constantly fighting at scale:

Instagram: The Media Processing WallInstagram's biggest challenge is decoding massive images and videos instantly on the fly. To keep your feed smooth, they aggressively compress media on server-side content delivery networks (CDNs) based on your exact screen size, and proactively pre-fetch the next three posts before you even scroll to them.

WhatsApp: The Encryption BottleneckBecause WhatsApp is strictly end-to-end encrypted, your device has to do heavy math to encrypt and decrypt every single text, photo, and voice note locally. This uses a massive amount of CPU power. To keep the app from lagging, teams offload these intense calculations away from the main user interface thread into concurrent native background threads via high-speed native interfaces.

Uber: The Re-render StormWith thousands of drivers moving simultaneously, Uber receives a constant waterfall of GPS coordinates. If the map re-rendered the entire screen every time a driver moved an inch, phones would overheat and crash. Uber fixes this by completely isolating state elements—allowing only the tiny car icon to re-draw its position while the rest of the map landscape remains completely untouched.

Netflix: Adaptive Streaming Under PressureNetflix has to deliver high-quality video across everything from low-end mobile phones in rural areas to 4K TVs on high-speed fiber lines. Their layout structures must adapt instantly to different screen sizes, applying aggressive, real-time content downscaling the millisecond your bandwidth drops to protect playback integrity and prevent buffering wheels.

15.Tradeoffs and architectural decisions teams make at scale

When apps grow to serve millions of users, there is no single "perfect" piece of code—there are only tradeoffs. Engineering teams must constantly balance competing priorities to keep their systems afloat:

Speed vs. Accuracy (Consistency vs. Availability): Teams must choose between showing data instantly or ensuring it is 100% accurate. For example, Instagram will show you a cached feed immediately so the app feels fast, even if it means you miss a post uploaded two seconds ago. Conversely, a banking app or Uber's ride-pricing engine must prioritize absolute accuracy over pure speed.

Feature Velocity vs. App Performance: Using highly abstracted frameworks and libraries allows developers to build and ship features incredibly fast. However, it adds weight to the app bundle. At scale, companies like WhatsApp constantly evaluate when to use flexible cross-platform tools and when to spend weeks writing highly customized, raw native code to save device memory.

Monolith vs. Micro-Frontends: Keeping code in a single repository is easy to manage at first, but it slows down large teams due to overlapping code changes. Moving to a modular, feature-based architecture keeps teams independent but introduces complex setup requirements for sharing data across those features.

Conclusion: Wrapping It All Together

Building a modern mobile powerhouse like Instagram, WhatsApp, Uber, or Netflix requires a deep shift from just making code work to making code survive.

As we have explored, architecture matters because it forms the invisible skeleton of your app—preventing performance traffic jams, protecting phone batteries, and allowing massive, multi-team engineering organizations to build simultaneously without breaking each other's features. Frameworks like Expo Router have fundamentally changed the game by aligning file structures directly with user experience layouts, making robust patterns like protected authentication flows and shared video contexts intuitive to build.

When you layer this routing with specialized state management, offline-first sync pipelines, real-time engines, and smart startup optimizations, you transform a fragile mobile application into a resilient digital ecosystem capable of handling the chaotic demands of global scale.

Top comments (0)