Mobile — Mobile App Architecture
Feature-Based vs Layer-Based Folder Structure in RN
Direct answer
For any React Native app beyond a handful of screens, feature-based folders beat layer-based ones. Grouping by feature (checkout, profile, orders) keeps everything a change touches in one place, while layer-based structures (components/, screens/, utils/) scatter each feature across the tree and turn shared folders into dumping grounds. Use feature folders with a small shared core, and enforce the boundaries with lint rules so features only import each other's public exports.
Folder structure sounds cosmetic until you inherit a two-year-old React Native codebase where deleting a feature means archaeology across six top-level directories. The structure you pick decides how expensive change is. Here is the comparison I walk teams through, and where I land.
Key facts, with sources
- React Native's New Architecture became the default in 0.76, the legacy bridge was retired in 0.82, and Hermes V1 shipped as the default JavaScript engine in 0.84. (TO THE NEW Blog)
- Microsoft retired Visual Studio App Center and CodePush on March 31, 2025, forcing every team that depended on it to migrate their over-the-air update architecture. (microsoft/react-native-code-push GitHub issue)
- In the State of React Native 2024 survey, Redux drew the most negative feedback at around 18% dissatisfaction, while React's built-in state management (31% positive) and Zustand (21% positive) were the best regarded. (InfoQ)
- Over 80% of State of React Native 2024 respondents work in teams of up to five developers, meaning most mobile architectures must be maintainable by very small teams. (SSOJet (State of React Native 2024 highlights))
- Published production examples report Shopify at 86% unified code across its apps and Instagram sharing 85 to 99% of code between iOS and Android. (CatDoes)
Why layer-based structures decay
Layer-based layouts — components/, screens/, hooks/, utils/, services/ — feel organized on day one because every file has an obvious home. The decay starts around the second dozen screens. A single change to checkout now touches five directories. Nobody can answer the question "what code belongs to this feature?" without a global search, so dead code accumulates because deleting anything feels risky.
The utils/ folder is the clearest symptom. It starts with date formatting and ends as a junk drawer of two hundred functions with unknowable dependents. In code audits I run, the size of utils/ relative to the codebase is one of my quickest proxies for how painful the project will be to modify.
What feature-based structure actually looks like
Feature-based means the top-level unit is a product capability, not a technical kind. Each feature folder contains its own components, hooks, state, and API calls, and exposes a deliberate public surface through an index file. Alongside features sits a shared/ folder for the design system and generic utilities, and an app/ folder for navigation, providers, and bootstrapping.
The test of a good feature boundary is deletability: removing the folder and fixing the resulting import errors should fully excise the feature. When a client asks me to cut scope before a launch, this structure turns that conversation from a week of surgery into an afternoon.
src/
features/
checkout/
components/
hooks/
store.ts
api.ts
index.ts // public surface — the only allowed import path
orders/
profile/
shared/
ui/ // design-system primitives
lib/ // date, currency, analytics helpers
app/
navigation/
providers/The shared folder is where discipline dies
Every feature-based codebase has a shared layer, and it is the most common failure point. The moment two features need the same button, someone promotes it to shared/ — fine. The problem is promoting business logic the same way: a "shared" order formatter quietly couples three features together, and now shared/ is a layer-based structure growing inside your feature-based one.
My rule: shared/ holds things that are generic to the point of being product-agnostic — UI primitives, date math, analytics wrappers. If a piece of logic mentions a domain concept, it belongs to a feature, and other features import it through that feature's public index. Duplicating twenty lines is often cheaper than creating a coupling point you can never remove.
Enforce boundaries or they are decoration
Folder conventions without enforcement last until the first crunch. I wire up import linting so the structure defends itself: features may not deep-import each other's internals, only index files; shared/ may not import from features/; app/ can import anything. ESLint with an import-boundary plugin, or a monorepo tool with dependency constraints, both work.
The first week after enabling the rules is annoying because it surfaces every existing violation. That list is valuable — it is a literal map of your coupling debt. I usually fix the cheap violations immediately, add ignore comments with ticket references for the expensive ones, and burn them down over a few sprints rather than blocking the release.
Migrating an existing layer-based codebase
Do not schedule a big-bang restructure; it will conflict with every open branch and stall for months. I migrate one feature at a time: pick the feature you are about to work on anyway, create its folder, move its screens and hooks in, and leave a re-export at the old path so nothing else breaks. Each product task pays a small structure tax until the tree is converted.
Start with the feature that changes most often — that is where the payoff compounds fastest. Leave stable, rarely-touched code for last; restructuring code nobody edits is effort spent where it earns nothing. Typically a mid-sized app converts fully within a couple of months of normal roadmap work.
When to hire senior help
Architecture is the cheapest place to buy senior expertise, because decisions about state management, navigation, offline strategy, and update infrastructure made in week one determine costs for years. A short engagement with a senior mobile architect before or during MVP planning routinely prevents the rewrite-at-scale scenario that hits teams around their first major growth phase. If your stack includes React Native + Python + AI, a senior engineer who owns the full product beats coordinating multiple juniors.
Bottom line
Dhairya Senjaliya ships Mobile — Mobile App Architecture projects worldwide — book a scoping call to discuss your specific situation.
Common pitfalls to avoid
- ✕Adopting Redux with sagas and heavy boilerplate for a five-screen MVP when built-in React state or Zustand would cover the actual requirements
- ✕Scattering business logic inside UI components instead of isolating a data layer, making later backend changes or native module swaps expensive
- ✕Building deployment architecture on a hosted OTA service with no exit plan, a risk the March 2025 CodePush shutdown made concrete for thousands of teams
- ✕Assuming permanent connectivity and bolting on caching later, instead of designing offline storage and sync conflict resolution before the data layer hardens
Frequently asked questions
When is a layer-based folder structure fine in React Native?
For apps with fewer than roughly ten screens and one or two developers, layer-based folders are fine — the whole codebase fits in your head, so locality does not matter yet. The trap is staying there too long. Once two people work on different features simultaneously or a feature spans five directories, switch before the utils folder becomes load-bearing.
How do features share code without creating a mess?
Two sanctioned paths: genuinely generic code moves to a shared layer that never references domain concepts, and domain logic stays owned by one feature and is imported through that feature's public index file. Enforce both with import lint rules. When neither fits cleanly, duplicating a small amount of code is usually cheaper than inventing a coupling point.
Should navigation live inside feature folders or at the app level?
I keep the root navigator and tab structure in an app-level folder, because it composes multiple features and owns deep linking. Each feature can export its own stack of screens for the root to mount. That way features stay self-contained, but there is exactly one place to understand the app's overall navigation shape.
What state management should a new mobile app use?
For most apps, React's built-in state plus a light library like Zustand is enough; in the State of React Native 2024 survey those two drew the most positive sentiment while Redux drew the most negative at about 18% dissatisfaction. Heavier tooling is justified mainly by large teams, complex shared state, or strict audit requirements.
Do we need offline support from day one?
If users operate in the field, in transit, or in markets with unreliable networks, yes, because retrofitting offline-first sync onto an online-only data layer is one of the most expensive refactors in mobile. If the app is unusable without live data anyway, graceful error and retry handling may be sufficient.
What are over-the-air updates and should our app use them?
OTA updates push JavaScript-level fixes directly to users without waiting for app store review, which is valuable for hotfixes. Microsoft's CodePush was retired on March 31, 2025, so current options are EAS Updates, a self-hosted CodePush server, or third-party services, and updates must stay within store policies that prohibit changing an app's core purpose.
Bottom line: Dhairya Senjaliya ships Mobile — Mobile App Architecture projects worldwide. Book a scoping call at https://dhairyasenjaliya.com/#book-call.