Project Architecture

Every folder.
One job.

A reference for where code lives, why it lives there, and when to promote it.

1 feature uses it?features/[name]/
2+ features use it?promote to root
3rd party setup?lib/
pure fn, no imports?utils/
env vars / enums?constants/
All Folders
📦
components/
shared

Truly reusable, dumb UI atoms. No business logic. No API calls. Pure presentation.

Use when Used by 2+ features or is framework-level UI
Button Input Modal Badge Toast Spinner
🧩
features/
domain

Self-contained domains. Each feature owns its own components, hooks, services, and types. Everything related to one domain lives here.

Use when Logic, UI, or data belongs to one specific domain
auth/ dashboard/ products/ billing/
📄
pages/
route-level

Route-level views only. Pages compose features + layouts. Zero business logic here — just glue code.

Use when It's a full route/URL destination
Home.tsx Login.tsx Dashboard.tsx Products.tsx
🪟
layouts/
shared

Page shells and wrappers. Contains nav, sidebar, and slot for page content. Wraps pages, not features.

Use when Multiple pages share a shell structure
AppLayout AuthLayout DashboardLayout
🪝
hooks/
shared

Shared custom hooks only. Feature-specific hooks live inside features/[name]/hooks/. Promote here when 2+ features need the same hook.

Use when Hook is used across multiple features
useDebounce useLocalStorage useMediaQuery
🌐
services/
shared

API calls and external data fetching. No UI logic. Returns raw data. Feature-specific services live in features/[name]/services/.

Use when Fetching/mutating data used by 2+ features
api.ts user.service.ts upload.service.ts
🗄️
store/
global

Global client state only. Local component state stays in the component. Only put state here if multiple disconnected components need it.

Use when State needed across unrelated parts of app
auth.slice.ts cart.slice.ts ui.slice.ts
⚙️
lib/
3rd party

Third-party library configuration and instantiation only. Not business logic. One file per vendor.

Use when Setting up / configuring external packages
axios.ts supabase.ts queryClient.ts firebase.ts
🔧
utils/
shared

Pure functions with zero side effects. No imports from your own codebase. Input → output, nothing else.

Use when Pure transformation, no app context needed
formatDate formatCurrency cn() validators
📌
constants/
global

Magic values, enums, route names, env var wrappers. No logic. No functions. Static values only.

Use when Value is reused and never computed
routes.ts enums.ts config.ts api.ts
🏷️
types/
global

Global TypeScript types and interfaces only. Feature-specific types live in features/[name]/types/. Promote when shared across features.

Use when Type used by 2+ features or is app-wide
api.types.ts user.types.ts common.types.ts
🎨
styles/
global

Global CSS only — resets, CSS variables/tokens, base typography. Component-level styles stay with the component (CSS modules / Tailwind).

Use when Style applies to entire app, not one component
globals.css variables.css typography.css
🖼️
assets/
global

Static files imported by the app. Images, icons (SVG), and fonts. Not served directly — bundler handles imports.

Use when Static file referenced in code via import
images/ icons/ fonts/
🗃️
database/
fullstack only

DB schemas, migrations, and raw queries. Fullstack / Next.js apps only. Drop this folder for pure frontend SPAs.

Use when App has server-side DB access (Next.js, Remix)
schema.ts migrations/ queries/
src/ — root structure
src/ ├── assets/ │ ├── images/ │ ├── icons/ │ └── fonts/ ├── components/ │ ├── ui/ │ │ ├── Button.tsx │ │ ├── Input.tsx │ │ ├── Modal.tsx │ │ └── Badge.tsx │ ├── forms/ │ │ ├── FormField.tsx │ │ └── FormError.tsx │ └── feedback/ │ ├── Toast.tsx │ ├── Spinner.tsx │ └── EmptyState.tsx ├── constants/ │ ├── routes.ts │ ├── api.ts │ ├── enums.ts │ └── config.ts ├── database/ │ ├── schema.ts │ ├── migrations/ │ └── queries/ │ ├── user.queries.ts │ └── product.queries.ts ├── hooks/ │ ├── useDebounce.ts │ ├── useLocalStorage.ts │ └── useMediaQuery.ts ├── layouts/ │ ├── AppLayout.tsx │ ├── AuthLayout.tsx │ └── DashboardLayout.tsx ├── lib/ │ ├── axios.ts │ ├── supabase.ts │ └── queryClient.ts ├── pages/ │ ├── Home.tsx │ ├── Dashboard.tsx │ ├── Login.tsx │ └── Products.tsx ├── services/ │ ├── api.ts │ ├── user.service.ts │ └── upload.service.ts ├── store/ │ ├── index.ts │ ├── auth.slice.ts │ ├── cart.slice.ts │ └── ui.slice.ts ├── styles/ │ ├── globals.css │ ├── variables.css │ └── typography.css ├── types/ │ ├── index.ts │ ├── api.types.ts │ ├── user.types.ts │ └── common.types.ts ├── utils/ │ ├── formatDate.ts │ ├── formatCurrency.ts │ ├── cn.ts │ └── validators.ts └── main.tsx
features/ — domain structure
features/ ├── auth/ │ ├── components/ │ │ ├── LoginForm.tsx │ │ ├── RegisterForm.tsx │ │ └── OAuthButton.tsx │ ├── hooks/ │ │ ├── useAuth.ts │ │ └── usePermissions.ts │ ├── services/ │ │ └── auth.service.ts │ ├── types/ │ │ └── auth.types.ts │ └── index.ts ├── dashboard/ │ ├── components/ │ │ ├── StatsCard.tsx │ │ ├── RevenueChart.tsx │ │ └── ActivityFeed.tsx │ ├── hooks/ │ │ └── useDashboardData.ts │ ├── services/ │ │ └── dashboard.service.ts │ ├── types/ │ │ └── dashboard.types.ts │ └── index.ts ├── products/ │ ├── components/ │ │ ├── ProductCard.tsx │ │ ├── ProductFilter.tsx │ │ └── ProductGrid.tsx │ ├── hooks/ │ │ ├── useProducts.ts │ │ └── useProductFilter.ts │ ├── services/ │ │ └── product.service.ts │ ├── types/ │ │ └── product.types.ts │ └── index.ts └── billing/ ├── components/ │ ├── PlanCard.tsx │ └── InvoiceList.tsx ├── hooks/ │ └── useBilling.ts ├── services/ │ └── billing.service.ts ├── types/ │ └── billing.types.ts └── index.ts — each feature mirrors same structure — components/ hooks/ services/ types/ index.ts — promotion rule — feature/X used in 2+ features → move to root folder
shared — 2+ features
feature-owned — 1 domain
global — app-wide
route / layout / config
fullstack only
Where does it go?

The placement checklist

Only one feature uses this component?
features/[name]/components/
Two or more features use this component?
components/ui/
It's a full page / route view?
pages/
It wraps multiple pages with nav/sidebar?
layouts/
It's a custom hook used in one feature?
features/[name]/hooks/
It's a custom hook used in many features?
hooks/
It sets up a third-party library?
lib/
It's a pure function with no side effects?
utils/
It's a static value, enum, or route name?
constants/
It's a TypeScript type for one feature?
features/[name]/types/
It's a TypeScript type used app-wide?
types/
It's global state needed across unrelated parts?
store/
It's an API call for one feature?
features/[name]/services/
It's an API call used across features?
services/