The best folder structure for an Expo Router project keeps a hard line between routes and everything else: the app/ directory holds only screens and layouts, while components, hooks, and utilities live in sibling folders. Use route groups in parentheses to organize signed-out, tabbed, and modal areas without polluting your URLs. That single rule keeps the project navigable as it grows.
The recommended layout
.
├── app/ # Routes only — file-based navigation
│ ├── _layout.tsx # Root layout (providers, theme, auth gate)
│ ├── (auth)/ # Signed-out group — no URL segment
│ │ ├── sign-in.tsx
│ │ └── sign-up.tsx
│ ├── (tabs)/ # Main tabbed app
│ │ ├── _layout.tsx # Tab bar
│ │ ├── index.tsx # Home
│ │ └── settings.tsx
│ └── [id].tsx # Dynamic route
├── components/ # Reusable UI (Button, Card, ...)
│ └── ui/
├── hooks/ # useAuth, useTheme, ...
├── lib/ # Clients, helpers, constants
├── constants/ # Colors, config
└── assets/ # Images, fonts
Why this works
Routes stay obvious
Because app/ contains nothing but routes and _layout files, you can read the folder tree and immediately understand your app's navigation. Mixing components in would make that impossible.
Route groups organize without leaking into URLs
Parentheses folders like (auth) and (tabs) group screens and share a layout, but the group name never appears in the path. This is how you separate the signed-out flow from the main app cleanly.
Layouts compose top-down
Each _layout.tsx wraps the routes beneath it. The root layout is the place for global providers (theme, auth, safe-area); nested layouts define navigators like a stack or tab bar.
Conventions that keep it maintainable
- One responsibility per layout. Root = providers and the auth gate.
(tabs)/_layout= the tab bar. Don't overload them. - Co-locate route-specific pieces sparingly. A small component used by exactly one screen can sit next to it, but shared UI belongs in
components/. - Use a path alias. Configure
@/*intsconfig.jsonso imports read@/components/ui/Buttoninstead of long relative paths. - Keep data access in
lib/. Your Supabase or API client lives here, not inside screens.
Naming your identifiers
While you're setting up a new project, you'll also need a valid slug, bundle identifier, and URL scheme for app.json. Generate them from your app name with our free Expo App Config Generator.
A ready-made starting point
Every DeveloperMill Expo template ships with this structure already in place — route groups, a typed @/* alias, and a components/ui kit — so you start from a clean, scalable base instead of an empty app/ folder.