A team-lead's breakdown of 8 real React Native project architectures — what each one actually solves, where the "Domain-Driven" and "Micro-Frontend" labels get misused, and how to pick one without over-engineering an MVP.
The house-building analogy
When you build a house, the labor that lays the bricks gets paid well. The architect who drew the blueprint gets paid more — because the architect already accounted for the second floor you'll add next year, and made sure the foundation could take the load without anyone tearing down a wall later.
React Native codebases work the same way. The folder structure you pick on day one either lets your app absorb 10 more features and 40 more engineers, or it collapses under its own weight and someone gets hired specifically to rewrite it.
This is also, almost word for word, what a React Native team lead interview is probing for: "Walk me through how you'd structure a project" or "What's your folder structure and why?" Nobody wants your code in that answer — they want to hear you reason about trade-offs. So here are eight real folder structures, what each one actually solves, and two places where the common naming gets sloppy.
1. Flat Structure — for prototypes and MVPs
src/
├── App.js
├── HomeScreen.js
├── ProfileScreen.js
├── Button.js
├── Card.js
└── api.js
Everything in one src/ folder, no categorization.
When to use it: a client demo, a hackathon build, a single-screen proof of concept — anything with a short shelf life, or code you expect a bigger team to re-architect later.
Where it breaks: past 10–15 files you're scrolling through an undifferentiated pile with no signal about what belongs together.
2. Feature-Based Structure — the industry default
src/
└── features/
├── auth/
│ ├── components/
│ ├── screens/
│ └── services/
├── profile/
│ ├── components/
│ ├── screens/
│ └── services/
└── feed/
├── components/
├── screens/
└── services/
This is the most common structure in production RN apps. Each product area — auth, profile, feed — owns its full vertical slice: components, screens, and API services.
Real payoff: shipping a new feature like Stories means adding features/stories/ and touching nothing else. It also maps to team ownership — one engineer owns auth/, another owns feed/, and merge conflicts across features mostly disappear.
The pitfall nobody mentions: feature folders don't enforce isolation by themselves. Nothing stops feed/services/feedApi.ts from importing directly from auth/services/authApi.ts, and once two or three of those cross-imports exist, you've quietly rebuilt a tangled flat structure inside feature folders. If you want the isolation to actually hold, enforce it with lint rules, not convention:
// .eslintrc.js
rules: {
'import/no-restricted-paths': ['error', {
zones: [
{ target: './src/features/auth', from: './src/features/feed' },
{ target: './src/features/feed', from: './src/features/auth' },
],
}],
}
Best for: medium-to-large apps with genuinely separable product areas — most consumer and B2B apps.
3. Layered Structure — separation of concerns
src/
├── components/ (shared UI components)
├── screens/ (all screens)
├── services/ (API calls)
├── utils/ (helper functions)
└── hooks/ (custom hooks)
Group by technical role instead of by feature: all screens together, all components together, all API logic together.
This was the default in a lot of older RN codebases and still works fine for small teams. Its failure mode is the opposite of feature-based: components/ and screens/ become dumping grounds with zero indication of which feature a file belongs to, and you end up scrolling past 40 unrelated screens to find the one you need.
Best for: small-to-medium apps where the team reasons by technical layer rather than by product feature.
4. Domain-Based Structure — not quite "DDD," and that's fine
src/
└── domains/
├── user/
│ ├── components/
│ ├── screens/
│ └── services/
└── events/
├── components/
├── screens/
└── services/
You'll see this called "Domain-Driven Structure" a lot, including in the video this article is based on — worth a correction: this isn't Domain-Driven Design in Eric Evans' sense. Real DDD involves bounded contexts, aggregates, entities, and a shared ubiquitous language with domain experts, and it's a modeling discipline, not a folder convention. What's actually happening here is simpler and still valuable: you're grouping by business capability instead of by UI feature, and a domain often maps to an actual team.
Say you have 500 engineers: 100 work exclusively on user (identity, accounts, permissions), 80 work on events. The folder structure mirrors the org chart — each domain is a slice a sub-team can own end-to-end, largely insulated from what other domains are doing.
This is the structure large product companies converge on, and getting domain boundaries wrong — user and events sharing implicit state, or one domain's screen directly navigating into another domain's internal component instead of through its exported API — is what causes expensive cross-team friction later.
Best for: large-scale products with genuinely distinct business capabilities and multiple sub-teams.
5. Atomic Design Structure — for design systems and UI libraries
src/
└── components/
├── atoms/ (Button, Input, Text)
├── molecules/ (SearchBar, FormField)
├── organisms/ (Header, ProductCard)
├── templates/ (PageLayout)
└── pages/ (fully composed screens)
This is UI component architecture, not app architecture. Based on Brad Frost's Atomic Design methodology: atoms are indivisible pieces (a button, a text input), molecules combine atoms into small functional units (a search bar), organisms combine molecules into bigger sections (a header), up through templates and full pages.
Best for: teams building a shared UI/component library consumed by multiple apps — not for organizing general app features.
6. Duck Structure — Redux-heavy apps
src/
└── ducks/
├── auth/
│ ├── actions.js
│ ├── reducer.js
│ └── types.js
└── profile/
├── actions.js
├── reducer.js
└── types.js
"Ducks" colocates a reducer's actions, types, and logic in one folder instead of splitting actions/, reducers/, and types/ into separate top-level folders — the older Redux convention. Redux Toolkit's createSlice has mostly absorbed this idea into the library itself, so you're more likely to inherit a ducks structure in an older codebase than to choose it fresh today.
Best for: recognizing and working with legacy heavily-Redux codebases.
7. Monorepo Structure — sharing logic between React Native and React
apps/
├── mobile/ (React Native app)
│ ├── src/
│ └── e2e/
└── web/ (React app)
└── src/
packages/
├── shared-components/
├── shared-business-logic/
└── shared-utils/
This matters once your product is more than a mobile app. If your Redux Toolkit store, API layer, and validation logic are the same on web and mobile, writing them twice isn't just wasted effort — it's a guaranteed source of drift (a bug fixed on web and forgotten on mobile).
A packages/ layer holds the shared business logic, components, and utils once; both apps/mobile and apps/web import from there.
Real example: an e-commerce company can define cart logic, pricing rules, and its API client once in packages/shared-business-logic, and both platforms consume the same source of truth.
Tooling: Yarn Workspaces or Lerna as a starting point, often paired with Nx or Turborepo once the repo is big enough to need build caching and task orchestration.
Best for: teams maintaining both web and mobile with substantial shared business logic.
8. Modular / "Micro-Frontend-Style" Structure — a label to use carefully
src/
└── modules/
├── auth-module/
├── profile-module/
├── chat-module/
└── shell/ (host app that composes them)
You'll hear this called "micro-frontend structure" for React Native, but that name is borrowed loosely from the web world and it's worth being precise about why. Web micro-frontends rely on runtime composition — Webpack/Vite Module Federation lets independently-built, independently-deployed bundles get stitched together in the browser at load time. React Native doesn't have that natively: there's one JS bundle, built and shipped as one app.
The closest real equivalent in RN is Re.Pack (Callstack's Module Federation implementation for React Native), or a native "super-app" plugin architecture where separate mini-apps are loaded as plugins into a host shell. Absent one of those, what most teams actually mean by this structure is: same idea as feature-based or domain-based organization, but with a stricter mental model — each module is designed to be buildable and testable in isolation, even if it still ships inside one bundle.
Best for: large orgs anticipating independent release cycles per feature team, or actively adopting Re.Pack / a super-app model — not a default choice for most apps.
Choosing the right structure: a decision framework
There's no universally correct structure — only the right one for your current constraints. As a team lead, you decide based on three inputs:
- Project size and complexity — what does the actual requirements spec call for?
- Team size — how many engineers, and how will work be divided?
- Growth trajectory — short-lived MVP, or a 2–3 year scaling bet?
A worked example: a new social app
-
Day 1, small team: start with Feature-Based Structure.
auth,profile,chat,storieseach get a folder. A 5–10 person team can handchatto two engineers andstoriesto another two without stepping on each other. - Team grows to ~100: introduce a Monorepo, especially once a web dashboard needs to share business logic with the app.
- Team grows past that, with distinct sub-teams per business area: migrate toward Domain-Based Structure, where domains map to actual team boundaries.
Skipping this thinking early isn't automatically wrong — nobody knows on day one whether the product survives past MVP, and over-architecting something that never ships is its own waste. But the pattern holds: the moment revenue and user growth show up, companies hire senior engineers specifically to retrofit scalable architecture onto code that was never built for scale. A single 1,000-line file is a common find in these migrations, and untangling it is real, billable work.
What's next in this series
Next up: setting up a real monorepo with Yarn Workspaces, wiring shared Redux Toolkit state between a React Native app and a React web app, and configuring the GitHub Actions workflows that keep both in sync.
If you found this useful, follow the series — and drop a comment with the structure your current project actually uses. I'd genuinely like to know what's most common out there right now.
Top comments (0)