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.
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
- 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.
- 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.
- Shared means truly shared. A component goes into
src/componentsonly when two or more features use it. Until then, it lives in its feature. - 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 callsfetchdirectly. - 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.tsfor 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:
| Situation | Monorepo? |
|---|---|
| One mobile app with a separate backend repo | Usually no |
| Mobile app + Next.js web app sharing types and API clients | Yes |
| Mobile app + backend sharing validation schemas and types | Often yes |
| Several white-label apps from one codebase | Yes, 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
- Expo Router introduction, Expo
- Environment variables in Expo, Expo
- Work with monorepos, Expo
- TypeScript path aliases in Expo, Expo
- TanStack Query, TanStack
Related
Expo Router vs React Navigation in 2026: Which to Use and Why
Expo Router vs React Navigation in 2026: file-based routes vs config, deep links, web URLs, native tabs, what changed in SDK 56, and which to pick.
How We Cut React Native Startup Time from 3.8s to 1.5s
The levers that cut React Native startup time by about 60%: measuring cold start properly, Hermes, image optimisation and cache-first fetching.
Migrating to the New Architecture: A Field Guide from 8 Migrations
A step-by-step guide to migrating a React Native app to the New Architecture in 2026: auditing libraries, the 0.81 path, common breakages and testing.
