expo-bulletproof-structure
Bulletproof Expo Structure
Project structure pattern for Expo/React Native apps. Adapted from Bulletproof React for the mobile ecosystem with Expo Router's file-based routing.
Core Principle
The app/ directory is a thin routing layer. All business logic lives in features/. Shared code lives in top-level directories. The dependency graph flows one direction only.
Directory Structure
app/ # ROUTING ONLY — maps URLs to screens
(tabs)/ # Tab group layout
index.tsx # Home tab route
explore.tsx # Explore tab route
_layout.tsx # Tab navigator config
(auth)/ # Auth flow group
sign-in.tsx
sign-up.tsx
_layout.tsx
_layout.tsx # Root layout (providers, theme, navigation container)
modal.tsx # Modal routes
features/ # BUSINESS LOGIC — one folder per feature domain
home/
components/ # UI specific to this feature
hooks/ # Hooks specific to this feature
api/ # Data fetching (queries, mutations)
utils/ # Feature-specific utilities
store/ # Feature-local state (Zustand slice, context)
types.ts # Feature types
index.ts # Public API — REQUIRED
components/ # SHARED UI — used across multiple features
ui/ # Primitives: button, input, card, icon
layout/ # Screen wrappers, safe area, containers
lib/ # GLOBAL UTILITIES — API client, storage, analytics
hooks/ # GLOBAL HOOKS — use-color-scheme, use-theme-color
constants/ # GLOBAL CONSTANTS — theme, colors, config
types/ # GLOBAL TYPES — navigation types, API types
providers/ # CONTEXT PROVIDERS — auth, theme, app-providers
assets/ # Static files — images, fonts
What Goes Where
| Code | Location | Example |
|---|---|---|
| Route/screen entry point | app/ |
app/(tabs)/index.tsx |
| Feature screen UI + logic | features/<name>/components/ |
features/home/components/home-screen.tsx |
| Feature data fetching | features/<name>/api/ |
features/home/api/use-get-stamps.ts |
| Feature hooks | features/<name>/hooks/ |
features/home/hooks/use-stamps.ts |
| Reusable button/card/input | components/ui/ |
components/ui/button.tsx |
| Layout wrappers | components/layout/ |
components/layout/screen-wrapper.tsx |
| API client setup | lib/ |
lib/api-client.ts |
| Global hooks | hooks/ |
hooks/use-color-scheme.ts |
| Theme config | constants/ |
constants/theme.ts |
Dependency Rule
app/ --> features/ --> components/, lib/, hooks/, constants/, types/, providers/
app/imports fromfeatures/(and shared code for layouts)features/imports from shared code- Shared code NEVER imports from
features/— this is the most critical rule - Cross-feature imports go through
index.tsonly
Route Files Must Be Thin
Route files in app/ exist only to connect a URL to a screen:
// app/(tabs)/index.tsx — CORRECT
import { HomeScreen } from '@/features/home';
export default function HomeRoute() {
return <HomeScreen />;
}
NEVER put in route files:
- Data fetching
- Business logic
- Complex component trees
- State management
- Styled components beyond basic wrappers
Feature Module Anatomy
Every feature MUST have an index.ts barrel file. Subfolders are created on-demand.
Required
index.ts— public API. Other code imports ONLY from here.
On-Demand (create when needed)
components/— feature-specific UIhooks/— feature-specific hooksapi/— data fetching (React Query hooks, fetch wrappers)utils/— feature-specific helpersstore/— feature-local state (Zustand slice or context)types.ts— feature types
Public API Pattern
// features/home/index.ts
export { HomeScreen } from './components/home-screen';
export { useStamps } from './hooks/use-stamps';
export type { Stamp } from './types';
Other features and app/ files MUST import from @/features/home, never from @/features/home/components/home-screen.
Expo Router Conventions
Layouts
_layout.tsx— defines the navigator for a directory (Stack, Tabs, Drawer)- Every route group MUST have a
_layout.tsx
Route Groups
(tabs)/— bottom tab navigator(auth)/— auth flow (sign-in, sign-up)(app)/— authenticated app shell- Groups with
()don't affect the URL path
File Naming
index.tsx— default route for a directory[id].tsx— dynamic route parameter[...rest].tsx— catch-all routemodal.tsx— presented as modal (configured in parent layout)+not-found.tsx— 404 handler
Settings
// app/_layout.tsx
export const unstable_settings = {
anchor: '(tabs)', // Initial route group
};
Decision Guide
"Where do I put this code?"
- Is it a screen that maps to a URL? →
app/(thin, imports from features) - Is it business logic for one feature? →
features/<name>/ - Is it a UI component used by 2+ features? →
components/ - Is it a primitive UI element (button, input)? →
components/ui/ - Is it a utility used by 2+ features? →
lib/ - Is it a hook used by 2+ features? →
hooks/ - Is it a type used by 2+ features? →
types/ - Is it a context provider? →
providers/
"Should this be in features/ or components/?"
- If it contains business logic or data fetching →
features/ - If it's pure UI with props and no domain knowledge →
components/ - If in doubt, start in
features/. Move tocomponents/when a second feature needs it.
File Naming Conventions
- kebab-case for all files and directories
- Component files:
stamp-card.tsx - Hook files:
use-stamps.ts - Type files:
types.ts - Barrel files:
index.ts - Test files:
stamp-card.test.tsx(colocated with source) - Platform-specific:
icon-symbol.ios.tsx,icon-symbol.tsx(default)
More from b4r7x/agent-skills
react-design-patterns
Use when choosing a React component pattern — custom hooks, control props, compound components, headless components, render props, container/presentational, or other architectural patterns. Includes 13 patterns with decision guide and 2025 popularity ranking.
26human-commit
Generates human-like git commit messages based on staged or unstaged changes. Reads git diff, analyzes what changed, and outputs 3 natural commit message options that sound like they were written by a developer — not AI. This skill should be used when the user wants a commit message, asks "what should I write for commit", "generate commit message", "human like commit", "wiadomość do commita", or just asks for help committing.
24humanize-readme
Rewrites a README.md to remove AI slop — buzzwords, generic openers, fake enthusiasm, and formulaic structure — replacing it with direct, honest, human-sounding writing. This skill should be used when the user wants to humanize a README, remove AI-generated writing patterns, make documentation sound less like ChatGPT wrote it, or asks to "fix the README", "humanize readme", "remove AI slop", "make it sound human".
24improve-prompt
Transforms a rough, unpolished prompt idea into a precise, structured AI coding prompt. Automatically researches the current project context (stack, file structure, conventions, git history) before generating. This skill should be used when the user provides a vague or "dirty" prompt idea and asks to refine, improve, or rewrite it — e.g. "improve this prompt", "refine my prompt", "ulepszony prompt", "dopracuj prompt", or simply describes what they want done in rough terms.
23react-anti-patterns
Use when reviewing React code — especially AI-generated code — to catch common anti-patterns. Covers 18 anti-patterns with detection difficulty, including stale closures, state mutation, useEffect abuse, and boolean explosion.
21deep-plan
Takes a rough, unpolished prompt idea and autonomously turns it into an implementation plan. Researches the project deeply, asks clarifying questions, generates a precise internal prompt, then executes it to produce a structured plan with todos. Designed for plan mode. Use when the user gives a vague feature request, rough idea, or "dirty" prompt and wants a ready-to-execute implementation plan — e.g. "plan this", "deep plan", "turn this into a plan", "zaplanuj to", "zrób plan".
19