Designing a Scalable React Native + Expo Router Folder Structure
3 mins read

Designing a Scalable React Native + Expo Router Folder Structure


Over the years, one lesson has repeated itself across teams and products:

Your folder structure is not about aesthetics — it’s about decision-making at scale.

Recently, I’ve been working on a React Native app using Expo + Expo Router, and I want to share a structure that has worked exceptionally well for large, long-lived apps.

This post is meant to share insights with the dev community, especially folks building apps that go beyond MVPs.

Note: All the below folders will reside in src/ folder at the highest level.

src/

└── 📁src
    └── 📁app
    └── 📁components
    └── 📁config
    └── 📁hooks
    └── 📁lib
    └── 📁providers
    └── 📁screens  
    └── 📁utils
Enter fullscreen mode

Exit fullscreen mode

🧭 app/ — Routing as a First-Class Citizen

Expo Router shines when routes reflect user flow, not technical shortcuts.

└── 📁app
    └── 📁(authenticated)
    └── 📁(home-tabs)
    └── 📁(unauthenticated)
    ├── _layout.tsx
    └── index.tsx
Enter fullscreen mode

Exit fullscreen mode

Why this works so well:

(unauthenticated)

  • Login, OTP, onboarding
  • No tabs, no distractions
  • Clear boundary for auth guards

(authenticated)

  • Entry point after login
  • Handles app-level layouts, redirects, and global state

(home-tabs)

  • Only the screens that truly belong to bottom tabs
  • Everything else (modals, flows, detail screens) lives outside tabs

This makes the flow crystal clear:
Unauthenticated → Authenticated → Tab-based home → Non-tab flows
No guessing. No accidental tab nesting. No router spaghetti.

🧱 components/ — Design System, Not Random Reuse

The structure follows Atomic Design, but applied pragmatically:

components/
 ├── atoms
 ├── molecules
 ├── organisms
 └── templates
Enter fullscreen mode

Exit fullscreen mode

Key principles:

Atoms → Pure, reusable, testable UI primitives

Molecules → Small compositions with intent

Organisms → Feature-aware UI blocks

Templates → Layout patterns, not screens

This ensures:

  • UI consistency across the app
  • Easy refactors when design systems evolve

Components stay reusable without becoming generic junk drawers.

🧠 lib/ — The App’s Brain (Not a Dumping Ground)

Here, lib/ is intentionally structured:

lib/
 ├── auth
 ├── backend
 ├── implementation
 ├── interface
 └── vector-icon
Enter fullscreen mode

Exit fullscreen mode

backend/

  • API clients (Axios / fetch wrappers)
  • TanStack Query client setup
  • Server-state hooks
  • Backend data models
  • interface / implementation
  • Clear contracts
  • Platform-agnostic abstractions
  • Easy to mock, test, or replace later

Eg.

└── 📁backend
    └── 📁_models
    └── 📁server-state
        └── 📁queries
            ├── useGetThoughtOfDayApi.ts
        ├── query-client.ts
    └── 📁supabase
        ├── supabase-client.ts
        ├── supabase-safe-call.ts
    └── 📁supabase-db
        └── fetch-though-of-day.ts
Enter fullscreen mode

Exit fullscreen mode

auth/

  • Auth state, providers, and boundaries live together
  • No auth logic leaking into UI

This separation pays off when:

  • APIs change
  • You swap backend providers
  • You test without the network

📱 screens/ — Screens Are Not Routes
A subtle but important distinction.

screens/
 ├── authenticated
 └── unauthenticated
Enter fullscreen mode

Exit fullscreen mode

  • Screens contain UI + screen-level state
  • Routes (app/) only decide when a screen is shown
  • This keeps navigation thin and screens testable

Result:

Screens are portable. Routes are declarative.

🧰 utils/, hooks/, providers/ — Supporting the Scale

utils/

  • Pure logic, zero React dependency
  • Easy to test, easy to trust

hooks/

  • App-specific behavior
  • Not generic utilities disguised as hooks

providers/

  • Theme, query client, safe area, global app context
  • Single source of truth for app-wide concerns

🏗️ Why This Structure Scales

  • Scales across multiple teams
  • Encourages clear ownership
  • Reduces cognitive load for new engineers
  • Supports feature-based growth without rewrites
  • Works equally well for React Native + Web (Expo)

Most importantly, it reflects how users move through the app, not how the framework works internally.

💡 Final Thought

  • Frameworks evolve.
  • Product requirements change.
  • Teams grow.

A good folder structure doesn’t fight that — it absorbs it. Hope this helps someone designing their next large-scale React Native app. Would love to hear how others are structuring their Expo Router projects 👋

Hope this helps anyone designing a production-grade Expo Router app.
Would love to hear how others are structuring their apps 👇



Source link

Leave a Reply

Your email address will not be published. Required fields are marked *