Architecture & Performance· 5 min read

A React Native Project Structure That Scales Past Version 1

A React Native folder structure that scales: thin Expo Router routes, feature folders, shared primitives, one API layer, config and when to use a monorepo.

By

Full-Stack, Mobile & AI Engineer

Every React Native project starts tidy. Three months later there's a utils folder with 40 unrelated files, screens that fetch data four different ways, and a components directory nobody dares delete from. The structure didn't fail because it was wrong on day one; it failed because it had no rules for growth.

This is a React Native folder structure I'd use for a production app in 2026, built around Expo Router: where each kind of code lives, the rules that keep it clean as the app grows, and when it's worth moving to a monorepo. It's deliberately simple. Structures that need a diagram to explain them don't survive real teams.

Key takeaways

  • Keep routes thin. Files in app/ should mostly compose components and call hooks, not hold business logic.
  • Organise by feature, not by file type, once the app has more than a handful of screens.
  • Have one place for shared primitives (your design system), one API layer and one place for configuration.
  • Colocate tests and types with the code they describe.
  • Use a monorepo only when you genuinely share code between apps, such as a mobile app, a web app and a backend.

The structure

app/                     # Expo Router routes only
  _layout.tsx
  (auth)/
    sign-in.tsx
  (tabs)/
    _layout.tsx
    index.tsx
    bookings.tsx
  class/[id].tsx
src/
  features/
    bookings/
      components/        # BookingCard, BookingList
      hooks/             # useBookings, useCancelBooking
      api.ts             # bookings endpoints
      types.ts
      bookings.test.ts
    auth/
    profile/
  components/            # shared primitives: Button, Text, Card, Screen
  lib/
    api.ts               # one HTTP client, auth headers, errors
    storage.ts           # MMKV or secure storage wrapper
    analytics.ts
  theme/                 # design tokens: colours, spacing, typography
  config/                # environment and feature flags
assets/

The rules that keep it clean

  1. Routes compose, features implement. A route file reads params, picks the right feature components and hooks, and renders them. If a route file grows past a hundred or so lines, logic belongs in the feature.
  2. Features own their data. Each feature has its API calls, hooks and types. Other features import from a feature's public surface (for example its hooks), not its internals.
  3. Shared means truly shared. A component goes into src/components only when two or more features use it. Until then, it lives in its feature.
  4. One way to fetch data. One HTTP client in lib/api.ts, and one data layer, such as TanStack Query hooks inside features. No screen calls fetch directly.
  5. No dumping grounds. If you're about to create utils.ts, name it for what it does (formatPrice.ts, dates.ts) and put it near its users.

Testing that follows the structure

A good structure makes testing obvious. Pure business rules (pricing, eligibility, date logic) get fast unit tests next to the files they cover. Feature hooks get tests that mock the API layer once, in lib/api.ts, rather than every fetch call. Shared primitives get a few rendering and accessibility tests, because every screen depends on them. And a handful of end-to-end flows (sign in, the core action, payment) cover what users actually do. When tests live beside the code, they get updated with it, and nobody has to remember a separate test tree.

Why feature folders

Grouping by type (screens/, components/, hooks/, services/) works for small apps. As the app grows, a single feature's code ends up scattered across six folders, and deleting a feature means hunting through all of them. Feature folders keep everything about bookings in features/bookings, so changes, reviews and deletions stay local.

Path aliases

Avoid ../../../components/Button. Configure a path alias in tsconfig.json, which Expo's bundler understands:

{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": { "@/*": ["src/*"] }
  }
}

Then import with @/components/Button or @/features/bookings/hooks.

Configuration and environments

Keep environment-specific values (API URLs, public keys, feature flags) out of code:

  • Use app.config.ts for build-time configuration that differs by environment.
  • Use EXPO_PUBLIC_ environment variables for values the app needs at runtime, and never put secrets there, because they end up inside the app bundle.
  • Store EAS environment variables per build profile (development, preview, production).

When a monorepo makes sense

A monorepo (for example pnpm workspaces, optionally with Turborepo) is worth it when several apps genuinely share code:

SituationMonorepo?
One mobile app with a separate backend repoUsually no
Mobile app + Next.js web app sharing types and API clientsYes
Mobile app + backend sharing validation schemas and typesOften yes
Several white-label apps from one codebaseYes, or one app with tenants

A typical layout puts apps in apps/ (mobile, web, api) and shared code in packages/ (types, api-client, validation, ui tokens). Expo supports monorepos and configures Metro for them in recent SDKs, but each extra package adds build complexity, so share only what changes together.

From the field: I've built full-stack products where the mobile app, the web app and the backend evolve together, like Floey's consumer app, business app and multi-tenant backend. In setups like that, sharing types and API contracts saves real bugs. For a single app with its own backend, a clean feature-based structure in one repository is usually all you need.

Clean architecture without the ceremony

You'll find React Native templates with domain, data and presentation layers, repositories, use cases and dependency injection. For most apps, that's more structure than value. The useful parts are simple:

  • Business rules that don't depend on React or the network live in plain TypeScript functions you can test directly.
  • Network access goes through one API layer, so you can change endpoints or add auth in one place.
  • UI components receive data and callbacks rather than fetching on their own.

Add layers only when a real problem appears, such as several data sources for the same entity, not in anticipation of one.

FAQ

What is the best folder structure for a React Native app?

For most apps: Expo Router routes in app/ kept thin, feature folders in src/features holding each feature's components, hooks, API calls and types, shared primitives in src/components, one API client in src/lib, and design tokens in src/theme. Add a monorepo only when several apps share code.

Should I organise React Native code by feature or by type?

By feature, once the app has more than a few screens. Feature folders keep related code together, make changes and code reviews local, and let you delete a feature cleanly. Grouping by type scatters each feature across many folders.

Do I need a monorepo for React Native?

Only if you share code between several apps or services, such as a mobile app and a Next.js web app sharing types and API clients. For a single app, one repository with a clear structure is simpler and builds faster.

Where should API calls live in a React Native project?

In one shared HTTP client (handling base URL, authentication and errors) plus feature-level API modules and hooks, for example with TanStack Query. Screens and components should call hooks, not fetch directly.

Next steps

If your codebase has outgrown its structure, I can propose a target layout and move the app there incrementally, without freezing feature work. Get in touch, or see how I work as a React Native developer.

Sources

Bhuwanesh Sisodia

Bhuwanesh Sisodia

Full-Stack, Mobile & AI Engineer

Bhuwanesh Sisodia is a Full-Stack, Mobile & AI Engineer based in Mumbai, with 7+ years of experience and 50+ apps shipped to the App Store and Google Play. He works independently with startups and product teams worldwide.

About meGitHubLinkedInX

Related