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/
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
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/
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
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
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
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
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
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
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
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
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
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
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/
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/
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/