Directory Structure & File Naming
Directory & File Structure Rules
The most important part of structuring your directory, and naming files, is consistency. There is no "right way" to do either, but whatever way you choose to do it should be consistent and easy to understand. What I present below is my personal preferences, with the rationale on how I arrived to the conclusion that this is the optimal way to approach it.
├── app/
│ ├── _components/
│ │ ├── component-1.tsx
│ │ ├── component-n.tsx
│ │ └── __tests__/
│ │ └── ...
│ ├── (auth)/
│ │ ├── _schemas/
│ │ │ └── auth-schemas.ts
│ │ ├── signin/
│ │ │ ├── page.tsx
│ │ │ └── __tests__/
│ │ │ └── ...
│ │ └── signup/
│ │ ├── page.tsx
│ │ └── __tests__/
│ │ └── ...
│ ├── [feature]/
│ │ ├── _components/
│ │ │ ├── component-1.tsx
│ │ │ ├── component-n.tsx
│ │ │ └── __tests__/
│ │ │ └── ...
│ │ ├── _constants/
│ │ │ ├── feature-constants.ts
│ │ │ ├── feature-enums.ts
│ │ │ └── __tests__/
│ │ │ └── ...
│ │ ├── _utils/
│ │ │ ├── feature-utils.ts
│ │ │ └── __tests__/
│ │ │ └── ...
│ │ ├── page.tsx
│ │ └── __tests__/
│ │ └── ...
│ ├── favicon.ico
│ ├── globals.css
│ ├── layout.tsx
│ ├── middleware.ts
│ ├── not-found.tsx
│ └── page.tsx
├── components/
│ └── ui/
│ ├── button.tsx
│ ├── input.tsx
│ └── __tests__/
│ └── ...
├── db/
│ ├── db.ts
│ ├── db-schema.ts
│ ├── enums.ts
│ ├── relations.ts
│ └── migrations/
│ └── (migration files)
├── docs/
│ └── ...
├── errors/
│ ├── api-error.ts
│ └── __tests__/
│ └── ...
├── hooks/
│ ├── use-something.ts
│ ├── use-another-thing.ts
│ ├── use-example.ts
│ └── __tests__/
│ └── ...
├── lib/
│ ├── supabase/
│ │ ├── admin.ts
│ │ ├── client.ts
│ │ ├── constants.ts
│ │ ├── middleware.ts
│ │ ├── server.ts
│ │ └── __tests__/
│ │ └── ...
│ ├── utils/
│ │ ├── text-formatting-utils.ts
│ │ ├── number-formatting-utils.ts
│ │ ├── date-formatting-utils.ts
│ │ └── __tests__/
│ │ └── ...
│ └── utils.ts
├── models/
│ └── [model]/
│ ├── model-actions.ts
│ ├── model-enums.ts
│ ├── model-schemas.ts
│ ├── model-service.ts
│ ├── model-types.ts
│ └── __tests__/
│ └── ...
├── public/
│ ├── images/
│ ├── icons/
│ ├── fonts/
│ ├── videos/
│ └── docs/
├── scripts/
│ └── (one-off scripts)
├── components.json
├── .*rc
├── *.config.*
├── jest.setup.ts
├── next-env.d.ts
├── package.json
├── pnpm-lock.yaml
├── postcss.config.mjs
└── tsconfig.json
Below are the rules I follow to create modular, maintainable, and predictable codebases.
1. Use kebab-case for all file and folder names
Predictability in naming speeds up navigation and reduces errors. It also just looks nice.
2. Group related code by feature or route
Every route or feature folder should contain everything it needs: _components
, _utils
, _constants
, page.tsx
, and __tests__
. Encapsulation ensures each feature can be developed, tested, and maintained in isolation.
3. Keep global UI primitives in components/ui
Any component reused broadly across the app belongs here. Components in components/ui
should have generic names and no dependencies on specific business logic.
4. Use _components
for feature-scoped components
Components that only exist to serve a specific feature or route live in that feature’s _components
folder. They should rarely be imported outside of their parent feature. This guarantees boundaries between modules and helps prevent accidental dependencies between unrelated features. If they're crossing over features it should likely be in the components/
folder.
5. Colocate tests in __tests__
folders
Every folder that contains logic should also contain a __tests__
folder. Colocation makes it obvious what is tested and keeps tests from drifting out of sync. It also reduces friction when adding or updating tests.
6. Use models/
for domain logic
Each model lives in its own folder named after the model with model-actions.ts
, model-service.ts
, model-enums.ts
, model-schemas.ts
, and model-types.ts
. This is great for organization, and helps with the searchability of a file.
7. Use db/
exclusively for database schema and migrations
Your db/
directory should only contain schema definitions, enums, relations, and migration files. Avoid placing application logic or model services here. This is most compatible for Drizzle ORM with Supabase, so it may look completely different for you depending on your usage patterns.
8. Organize static assets under public/
subfolders
Separate assets by type: images
, icons
, fonts
, videos
, docs
. Clear asset organization prevents the public folder from becoming an unstructured dumping ground, making it easier to locate and clean up files over time.
9. Keep generic utilities in lib/utils
Only utilities that are broadly reusable and not tied to a specific feature should live in lib/utils
. Examples include text-formatting-utils.ts
, number-formatting-utils.ts
, and date-formatting-utils.ts
. This keeps your feature folders focused, your helpers discoverable, and prevents having an monstrous general util.ts
file.
10. Use _utils
for feature-specific helpers
If a utility only applies to a single feature or route, it belongs in _utils
within that feature folder. Avoid importing these helpers outside their module. This practice reinforces modular design and helps avoid circular dependencies.
11. Be consistent everywhere
Every feature folder should look the same. Every model folder should follow the same conventions. Consistency reduces cognitive load, makes code easier to read, and prevents subtle bugs from divergent patterns.
Consistent Code Is Good For AI (My Hypothesis)
We know that AI is largely a function of pattern recognition. If we provide bad or average patterns, AI will generate code with those patterns and your code will only degrade over time. If we provide good patterns, AI can generate higher quality code that can be more scalable over time.