Keep your app predictable, modular, and scalable from day one. This structure combines Bulletproof React ideas with a simple feature-first layout that avoids domain specifics and stays easy to navigate.
Goals
- Clear boundaries: Separate generic vs feature-specific code
- Fast imports: Use path aliases, rather than needlessly traversing the file structure in your code
- Repeatable pattern: “Copy the structure,” not reinvent it
Base Layout (app-wide)
Use a small set of top-level folders and stick to them.
Note that this structure exists under your 'src' folder, as the root of your project may differ based on what framework you are using (I am using Vite)
src/
app/ # App entry, providers, global styles, bootstrapping
routes/ # Route files (code-split where possible)
components/ # Highly generic, reusable UI only (no feature logic)
features/ # Feature modules (each self-contained)
hooks/ # Reusable cross-feature hooks
utils/ # Pure utilities and helpers
services/ # Integrations with external systems
types/ # Global/shared TypeScript types
store/ # Store logic and state management
assets/ # Static assets
- app: central wiring (providers, theming, error boundaries).
- routes: route-level code splitting and lazy loading.
-
components: generic-only; if you feel tempted to add feature logic here, it belongs in
features/
. - features: replicate a mini version of the base structure per feature (see below).
- store: generic state management code, like bootstrapping/exporting the main provider. Specific reducers/selectors will usually exist in their respective feature.
- services: any code required to connect with an external system
- hooks/utils/types: shareable and framework-agnostic where possible.
Path Aliases
Keep imports short and consistent:
import { Button } from "@components/ui/Button";
import { loadUser } from "@features/user/services/api";
import { formatDate } from "@utils/date";
-
Common aliases:
@/
,@components/
,@features/
,@hooks/
,@utils/
.
Your tsconfig paths
property will probably need to look something like this
"paths": {
"@/*": ["./src/*"],
"@app/*": ["./src/app/*"],
"@components/*": ["./src/components/*"],
"@features/*": ["./src/features/*"],
"@hooks/*": ["./src/hooks/*"],
"@types/*": ["./src/types/*"],
"@utils/*": ["./src/utils/*"],
"@store/*": ["./src/store/*"],
"@services/*": ["./src/services/*"],
"@assets/*": ["./src/assets/*"]
}
Features Folder Pattern
Each feature is a self-contained slice. Replicate the same internal pattern for every feature to scale cleanly. Below is an example, but the core idea is that you can/should copy the structure defined above to build suit the feature.
src/features/<feature-name>/
components/ # Feature UI (dumb-first; compose small pieces, but don't stress about making stuff 'overly-generic')
hooks/ # Feature-specific hooks
services/ # Data fetching, adapters, caching, side effects
types/ # Feature-only types/interfaces
index.ts # Public surface (barrel exports)
- Keep feature concerns inside the feature. Avoid reaching into other features. If you find you need to import a hook from another feature, then congratulations, you've just found a practical use case to create a generic hook
-
Export only what the rest of the app should use from
index.ts
. - If something becomes generic across features, promote it to
@components/
,@hooks/
, or@utils/
.
Bulletproof React Principles (essentials)
This structure is derived from Bulletproof React, which is a great foundation to expand upon depending on your needs.
Some of the core concepts are below:
- Feature-first: colocate logic, UI, and types by feature.
-
Barrel files (
index.ts
): define clear public APIs per folder. -
Type safety: strict TypeScript, no
any
; define props and service types. - Early returns over nesting: keep components/functions shallow.
- Small, focused modules: single responsibility; extract when files grow.
-
Pure utilities in
@utils/
: deterministic and easy to test.
How to Add a New Feature (repeatable)
- Create
src/features/<feature-name>/
. - Add
components/
,hooks/
,services/
,types/
,index.ts
(or add the required folders at the time you need them). - Build feature-specific UI in
components/
, stateful logic inhooks/
, side effects inservices/
. - Export the minimal public surface from
index.ts
. - Wire it into
routes/
. - Test at the feature boundary; add E2E coverage if it affects user flows.
What Goes Where (quick rules)
-
Generic UI:
@components/
-
Feature UI/logic:
@features/<feature>/
-
Reusable hooks:
@hooks/
-
Pure helpers:
@utils/
-
Shared types:
types/
-
Routes & splitting:
routes/
-
App bootstrapping:
app/
Conclusion
I hope you found this helpful. Feel free to share your thoughts on what works and what doesn't with this structure. Otherwise, happy developing and good luck on your next project.
Find me here:
https://www.linkedin.com/in/nicholas-fane-06205897/
https://github.com/NickFane/
Top comments (0)