Project Directory Structure
We follow a directory inspired by slice-based architecture, and tailor fit to our needs.
This is a modified version of slice-based architecture and Bulletproof React.
Root-level Directories
publicfor assetsscriptsfor anything scriptable (ie. seeding test data or copying files on build)serveraliased in tsconfig as@serverfor server-side code if we decide to have the server & frontend on the same repositorysrcappfor Next.js routing- it should only contain
page.tsxand the other files. - do not mix components & other files there
- it should only contain
componentsfor global, shared componentsuifor shadcn componentslayoutforlayout.tsxcomponentspagesforerror.tsxandloading.tsxpages
configfor single source of truth configurationapp.config.ts- OG metadata
- title, description, etc.
env.client.config.tsNEXT_PUBLIC_process.env variables
env.server.config.ts- any other process.env that shouldn't be shown in the client
constants- for stuff like
UNKNOWN_ICON_IMG_SRCto centralize string definitions to not repeat ourselves - rarely used
- for stuff like
hooks- global hooks like
use-is-mobile.hook.ts
- global hooks like
lib- for integrations with packages like
auth,i18n,payload,db,dexie, etc.
- for integrations with packages like
providers- for projecting React Context across many components
query-client.provider.tsxfor tanstack query
stores- for globally-scoped stores that are not scoped by feature
site-data.store.ts
stylescolors.csscolor theme definitionsfonts.cssfor fontsglobals.cssfor everything else + any third-party CSS imports liketw-animate-css
Feature-based Directories
We group features into their own directories inside src and never keep them at the root unless they are global & reusable.
For example:
src/features/achievementscomponentsachievement-group-card.tsx
hooksuse-achievements.tsuseShallowzustand hook to abstract the method of a zustand slice store
servicesachievements.service.ts- fetches data for rendering components in an RSC
slicesachievements.slice.ts- a composable zustand slice that we can use in a global store or with other features to compose bigger stores
typesachievement-category-enum.type.ts- exports
const AchievementCategoryEnumSchemaandtype AchievementCategoryEnum = z.infer<typeof AchievementCategoryEnumSchema>
- exports
utilsget-achievement-category-image.ts
We can then import these features in other parts of the application with proper aliasing.
import { type AchievementCategoryEnum } from @/features/achievements/types/achievement-category-enum.type