Feature-Based Structure
We split the app into two roots:
app/— Expo Router routes. This layer is thin: a route file just re-exports the real screen.src/— all the actual code, organized by feature.
app/ # Expo Router — routes only
_layout.tsx # providers (error boundary, query client, auth, safe area)
(auth)/ # public route group
(app)/ # authenticated route group
src/
features/<feature>/ # auth, profile, orders, ...
screens/ # one file per screen
components/ # components used only by this feature
hooks/ # feature hooks (queries, mutations, logic)
services/ # API calls for this feature
types.ts # feature-local types
index.ts # barrel — the feature's public surface
shared/
components/ config/ constants/ hooks/ services/ types/ utils/
assets/
The rules
-
Route files re-export screens. A file in
app/should be one line:// app/(app)/profile.tsx export { ProfileScreen as default } from "@features/profile"; - A feature owns its slice. Screens, components, hooks, services, and types
for a feature live together under
src/features/<feature>/. shared/is for genuinely cross-cutting code — the API client, design tokens, error boundary, generic hooks/utils. If only one feature uses it, it belongs in that feature.
Why feature-based beats layer-based? In a layer-based layout
(components/, hooks/, services/ at the top), one feature’s code is
scattered across the tree. Feature-based keeps things that change together in
one place, so a feature is easy to find, reason about, and even delete.