Free quote
Back to Blog
Article
August 23, 202616 min read

React Native Folder Structure Best Practices 2026: The Complete Guide

KB

Konrad Bachowski

Tech lead, HeyNeuron

React Native Folder Structure Best Practices 2026: The Complete Guide

React Native Folder Structure Best Practices 2026: The Complete Guide

The folder structure you pick on day one of a React Native project will either save you months of refactoring or cost you weeks of untangling spaghetti imports two years from now. With React Native now powering 1,350 of the top 10,000 iOS apps — representing 47% of downloads in that set — and npm downloads reaching 10.3 million per week as of July 2026, the ecosystem has matured enough that bad structure decisions are no longer just technical debt: they're hiring problems, velocity problems, and sometimes dealbreakers for clients.

This guide covers what most structure articles miss: the impact of Expo Router's file-based routing on how you organize screens, what the State of React Native 2024 survey (3,501 responses) tells us about how real teams structure their projects, and how to migrate an existing codebase without a full rewrite.

The Three Structural Patterns — and When Each Breaks

Most React Native projects land in one of three camps. Choosing wrong isn't fatal, but refactoring between patterns at scale is painful.

Layer-based (type-based) structure groups files by what they are — all components together, all hooks together, all services together:

src/
  components/
  hooks/
  services/
  screens/
  utils/

This works cleanly for apps with under 5 screens and 1-2 developers. It breaks the moment you add a third feature and suddenly components/ holds 80 files with no obvious relationship to each other.

Feature-based structure groups files by what feature they belong to:

src/
  features/
    auth/
      components/
      hooks/
      services/
      types.ts
    dashboard/
      components/
      hooks/

This scales better. Adding a new feature means creating one new folder, not scattering changes across 6 different directories. The downside: cross-feature dependencies are harder to govern, and shared code needs a clear home.

Hybrid structure is the pragmatic default most experienced teams settle on — feature-isolated modules under features/, with a separate components/ for truly global UI primitives:

src/
  features/     ← feature-isolated modules
  components/   ← global, reusable UI only
  hooks/        ← app-wide hooks
  lib/          ← API clients, third-party wrappers
  store/        ← global state
  types/        ← shared TypeScript definitions

The following decision table summarizes when each pattern fits:

Pattern Best for Team size Break point
Layer-based MVPs, prototypes, ≤ 5 screens 1-2 devs 3+ features
Feature-based Products with clear domain boundaries 2-5 devs Heavy cross-feature state
Hybrid Most production apps 3-10 devs Rarely — scales well

According to the State of React Native 2024 survey, 80%+ of teams have 5 or fewer developers. Hybrid is the right default for almost everyone reading this.

The Canonical 2026 Folder Structure (With Expo Router)

Expo is now the officially recommended way to start a React Native project — and 60% of React Native developers already use it (up from ~40% in 2023). But Expo Router's file-based routing creates a constraint that most structure guides ignore: every file in the /app directory automatically becomes a route.

This means you cannot put business logic, component variants, or hooks inside /app without accidentally creating routes. The correct pattern is to keep /app as thin route re-export layers and move all logic into /src:

my-app/
├── app/                     ← Expo Router routes (thin shells only)
│   ├── (auth)/
│   │   ├── login.tsx        ← re-exports src/features/auth/LoginScreen
│   │   └── register.tsx
│   ├── (tabs)/
│   │   ├── index.tsx
│   │   └── profile.tsx
│   ├── _layout.tsx
│   └── +not-found.tsx
│
├── src/                     ← all business logic lives here
│   ├── features/
│   │   ├── auth/
│   │   │   ├── components/
│   │   │   ├── hooks/
│   │   │   ├── services/
│   │   │   ├── AuthScreen.tsx
│   │   │   └── types.ts
│   │   └── dashboard/
│   │       ├── components/
│   │       ├── hooks/
│   │       └── DashboardScreen.tsx
│   │
│   ├── components/          ← global UI primitives (Button, Input, Modal)
│   ├── hooks/               ← app-wide hooks (useAuth, useTheme)
│   ├── lib/                 ← API client, analytics, third-party wrappers
│   ├── store/               ← Zustand slices or Redux store
│   ├── types/               ← shared TypeScript interfaces
│   ├── constants/           ← colors, sizes, routes enum
│   └── utils/               ← pure utility functions
│
├── assets/                  ← fonts, images, lottie
├── e2e/                     ← Maestro or Detox E2E tests
├── .env.local               ← secrets (never committed)
├── app.config.ts            ← dynamic Expo config
└── eas.json                 ← EAS Build profiles

The key insight: app/login.tsx might contain nothing but export { default } from '@/features/auth/LoginScreen'. The Expo Router file is the route registration; the screen component and its logic live in src/features/auth/.

State Management: Where Things Live

The State of React Native 2024 survey found that 94% of developers are interested in local-first architecture but only 25% have implemented it. Structure is part of why — most teams don't have a clear place for offline data layers.

For 2026 projects:

  1. Server state with React Query — query files live next to the feature that owns them: src/features/orders/queries.ts. Shared queries (e.g., current user) go in src/lib/queries/.

  2. Client state with Zustand — feature-scoped stores in src/features/[feature]/store.ts for local UI state; a single src/store/index.ts for truly global state (auth tokens, theme preference).

  3. Local-first / offline data — if you're using WatermelonDB, MMKV, or SQLite, create src/lib/db/ with schema definitions and migration files separate from your feature code. This keeps database schema changes reviewable in isolation.

  4. Context API — fine for theme and i18n providers. Avoid it for frequently-updated state; it will cause unnecessary re-renders. Providers go in src/components/providers/.

TypeScript Conventions That Prevent Barrel File Traps

TypeScript structure decisions have a hidden performance cost: overusing barrel files (those index.ts re-export files) slows Metro bundler and increases cold start times because Metro must resolve every export even if you only need one.

The practical rules:

  • Use barrel files (index.ts) in src/components/ and src/hooks/ where you have ≤ 20 exports. Stop there.
  • Do not create a barrel in src/features/[feature]/ — import directly from the specific file: import { LoginScreen } from '@/features/auth/LoginScreen' not from @/features/auth.
  • Global type definitions go in src/types/index.ts for interfaces shared across features, and in src/features/[feature]/types.ts for feature-local types.
  • Enable "baseUrl": "src" in tsconfig.json plus path aliases so imports stay clean: @/components/Button rather than ../../../components/Button.

Environment Configuration and Secrets

A folder structure guide that doesn't address secrets management is incomplete. The wrong approach — committing .env files or hardcoding API keys — has caused real data breaches.

For Expo projects in 2026:

my-app/
├── .env.local          ← local dev only, in .gitignore
├── .env.example        ← committed template (no real values)
├── app.config.ts       ← reads process.env, exposes via expo.extra

app.config.ts acts as the boundary between build-time environment variables and runtime app code:

export default ({ config }) => ({
  ...config,
  extra: {
    apiUrl: process.env.API_URL,
    analyticsKey: process.env.ANALYTICS_KEY,
  },
});

For production secrets (API keys, signing credentials), use EAS Secrets — they're injected at build time by EAS Build and never touch your source tree. Never commit secrets to eas.json.

Monorepo Structure for React Native + Web

Only 22% of React Native developers target web alongside mobile — but Expo Router's universal apps capability is growing, and if you're building a shared design system across web and mobile, a monorepo pays off quickly.

A Turborepo setup for a team shipping both:

monorepo/
├── apps/
│   ├── mobile/          ← React Native / Expo app
│   └── web/             ← Next.js app
├── packages/
│   ├── ui/              ← shared component library (React Native Web)
│   ├── utils/           ← shared TypeScript utilities
│   └── eslint-config/   ← shared linting rules
├── turbo.json
└── package.json

The packages/ui/ library uses React Native Web so components render correctly on both platforms without duplication. Path aliases in each app point to the shared packages via workspace references.

This pattern only makes sense if you genuinely share 30%+ of code between platforms. For most teams with distinct web and mobile products, a monorepo adds coordination overhead without benefit.

CI/CD and Build Pipeline Folder Conventions

Build configuration files need their own discipline. A common mistake is leaving eas.json, Fastlane files, and GitHub Actions workflows in random locations until the CI pipeline becomes unmaintainable.

Standard placement:

my-app/
├── .github/
│   └── workflows/
│       ├── ci.yml           ← lint, test, type-check
│       └── release.yml      ← EAS build + submit
├── eas.json                 ← EAS Build profiles (development/preview/production)
├── app.config.ts            ← Expo dynamic config
└── fastlane/                ← only if not using EAS Submit
    ├── Fastfile
    └── Appfile

eas.json defines your build profiles — keep development pointing at your local API, preview pointing at staging, and production at the live API. Never hardcode API URLs; read them from EAS environment variable groups instead.

Migrating an Existing Codebase

No migration guide — every competitor misses this. If your project is 18 months old with a flat src/ directory holding 200 files, here is a realistic incremental approach:

  1. Audit with ESLint import rules. Install eslint-plugin-boundaries and define your target zones (features, components, lib). The first run shows you exactly which files are importing across boundaries they shouldn't.

  2. Refactor one feature at a time. Pick the most-touched feature (likely auth or navigation). Create src/features/auth/, move its files there, fix imports, run tests. Merge. Repeat.

  3. Don't move tests separately. Co-locate each __tests__ folder with the feature it tests during migration, not after. Splitting migration and test migration doubles the churn.

  4. Red flags that indicate deeper structural debt:

  5. A utils/ folder with more than 30 files and no clear categories
  6. Components importing from other components 3+ levels deep
  7. Any file named helpers.ts, misc.ts, or common.ts with more than 200 lines
  8. Business logic inside navigation config files

The migration typically takes 2-4 weeks for a medium-sized codebase (50-100 screens) when done incrementally alongside normal feature work.

Common Mistakes to Avoid

Over-nesting. Anything beyond 4 directory levels deep is a sign you've over-segmented. src/features/auth/components/forms/inputs/TextInput.tsx is too deep; src/features/auth/components/TextInput.tsx is fine.

Mixing business logic into /app routes. If your Expo Router files contain more than 20 lines of logic, you've broken the pattern. Route files should be thin: one default export that returns a Screen component.

A single global components/ with 100+ files. This is a layer-based antipattern in disguise. Anything used by only one feature belongs in that feature's folder, not in global components/.

Ignoring .env management until you have a breach. Secrets in source control is a real risk — Stack Overflow Developer Survey 2024 shows React Native reaching 8.4% of all developers, meaning more projects, more targets, and more repos accidentally committed with real API keys.

Quick-Reference Checklist

Before merging your folder structure setup:

  • [ ] /app directory contains only Expo Router route files (each ≤ 30 lines, default export only)
  • [ ] All business logic lives under /src — no exceptions
  • [ ] Feature folders are self-contained — each has its own components/, hooks/, services/, and types.ts
  • [ ] Barrel files limited to components/ and hooks/ top-level (not inside features)
  • [ ] .env.local in .gitignore — verified with git status before first commit
  • [ ] EAS Build profiles configured in eas.json for dev/preview/production
  • [ ] Path aliases configured in tsconfig.json (@/src/)
  • [ ] ESLint import rules installed to enforce feature boundaries

Conclusion

A React Native project with clear folder structure is faster to onboard into, easier to test, and far less likely to turn into a rewrite-or-quit situation 18 months in. The Expo Router constraint — keeping /app thin and moving all logic to /src — is the single most important shift from pre-Expo Router guides. Everything else (feature-based organization, Zustand placement, EAS secrets management) follows logically once that boundary is established.

If you're starting a greenfield project and want an architecture review, or if you're considering hiring a team to build a React Native app with the right foundation, HeyNeuron's mobile app team works with Expo and React Native for B2B and SaaS mobile products. We can also help you choose the right tech stack before writing a line of code. Get in touch to discuss your project.


Related Articles


FAQ

What is the best folder structure for a React Native project in 2026?

The hybrid structure — feature-isolated modules under src/features/, shared UI in src/components/, API layer in src/lib/, and global state in src/store/ — scales best for teams of 2-10 developers. If you use Expo Router, keep /app directory files as thin route shells only, moving all business logic into /src.

Does Expo Router change how you organize React Native files?

Yes, significantly. Every file in Expo Router's /app directory automatically becomes a route. This means you cannot put screen logic, custom hooks, or component variants in /app without creating unintended routes. The correct pattern is a thin app/screen.tsx that re-exports a Screen component from src/features/[feature]/.

Should I use feature-based or layer-based folder structure for React Native?

Layer-based (grouping by type: all components together, all hooks together) works for prototypes and apps with fewer than 5 screens. Feature-based (grouping by product domain) works better once you have 3 or more distinct features and 2+ developers. Hybrid — feature folders for domain logic, shared directories for cross-cutting concerns — is the most practical default for production apps.

How should I handle TypeScript types in a React Native project?

Global types shared across features go in src/types/index.ts. Feature-local types live in src/features/[feature]/types.ts. Avoid one giant types.ts at the root level — it becomes a catch-all that's hard to navigate. Use "paths" in tsconfig.json for clean imports: @/types/User instead of ../../../types.

What is the right place for Zustand stores in React Native?

Feature-scoped state (UI state for one screen or feature) goes in src/features/[feature]/store.ts. Truly global state — auth tokens, theme, user preferences — goes in src/store/index.ts. Avoid putting everything in one global store; feature-scoped stores are easier to test and delete when a feature is removed.

How do I manage environment variables and secrets in React Native with Expo?

Use .env.local for local development (always in .gitignore), and app.config.ts as the boundary that reads process.env values and exposes them via expo.extra. For production secrets (API keys, signing credentials), use EAS Secrets — they're injected at build time and never touch your source tree. Never commit real values to eas.json or .env files.

When does a monorepo make sense for React Native?

A monorepo (Turborepo or Nx) is justified when you're sharing 30%+ of code between a React Native app and a web app — typically a shared component library, shared TypeScript utilities, or shared authentication logic. For teams with completely separate web and mobile products, a monorepo adds coordination overhead without benefit.

How do I migrate an existing React Native project to a better folder structure?

The safest approach is incremental: install eslint-plugin-boundaries to see which files violate the target boundaries, then refactor one feature at a time. Move its files into src/features/[feature]/, fix all imports, run tests, and merge. Avoid moving the entire codebase at once — it creates a massive diff that's impossible to review and likely to introduce bugs.

Stay up to date with AI and automation

Subscribe to our newsletter to receive specific tips and tools once a week. Join over 2,000 subscribers.

Your data is safe. Zero spam.