Project Structure and Conventions
BeginnerGroup by feature, not by file type. The folder layout that feels tidy at ten components is the one that becomes unnavigable at two hundred.
Overview
Every React tutorial creates components/, hooks/ and utils/, and it works fine until the app has real features. Then a single change touches four folders, and nobody can tell which of the sixty files in components/ belong together. Grouping by feature keeps everything one change needs in one place, and makes deleting a feature a matter of deleting a folder. The other convention worth adopting early is colocation: a component's test, styles and types live beside it, not in a parallel tree.
Feature Folders
The layout that survives growth.
src/
features/
problems/
ProblemList.jsx
ProblemCard.jsx
ProblemCard.test.jsx // colocated
useProblems.js // the feature's own hook
api.js // the feature's own requests
types.ts
auth/
LoginForm.jsx
useAuth.js
AuthProvider.jsx
components/ // shared, generic, no feature knowledge
Button.jsx
Modal.jsx
Spinner.jsx
lib/ // shared non-UI helpers
apiClient.js
formatDate.js
App.jsx
// The test: to delete "problems", delete one folder.
// With type-based folders you would hunt through four.
// A shared component must not import from features/.
// If it needs to, it is not shared — it belongs to that feature.Naming and File Conventions
Small consistent choices that make a codebase readable to someone new.
// PascalCase for components and their files
ProblemCard.jsx -> export function ProblemCard()
// camelCase with a use prefix for hooks — the linter depends on it
useProblems.js -> export function useProblems()
// One component per file, named the same as the file.
// Default vs named export: pick one and be consistent. Named exports
// rename safely and autocomplete better.
export function ProblemCard() {} // preferred
export default function ProblemCard() {} // fine, if it is the rule
// Path aliases, so imports stay readable after a move
// jsconfig.json / tsconfig.json
{ "paths": { "@/*": ["./src/*"] } }
import { Button } from '@/components/Button'
// instead of '../../../components/Button'Where Things Belong
A few decisions that recur, with a default answer for each.
// A component used by exactly one feature
// -> inside that feature folder, not components/
// A hook that wraps a fetch for one screen
// -> beside that screen; promote it only when a second caller appears
// Types shared between the API layer and the UI
// -> the feature's types.ts, or a shared package if the backend
// generates them (see the JavaScript track's API boundary concept)
// Constants
// -> next to what uses them. A global constants.js becomes a dumping
// ground that everything imports and nothing owns.
// Do not create an abstraction on the first repetition. Two similar
// components are cheaper to maintain than one component with six
// boolean props that neither caller fully understands.Key Points to Remember
- 1Group by feature rather than by file type, so one change touches one folder
- 2Colocate tests, styles and types beside the component they belong to
- 3A shared component that imports from a feature is not shared — move it into that feature
- 4Hooks must start with "use" for the lint rules to check them; components use PascalCase
- 5Path aliases keep imports stable across moves; avoid a global constants file nothing owns
Interview Questions
Sign in to ask AriaWhy is grouping by feature usually better than grouping by file type?
How do you decide whether a component belongs in a feature folder or a shared folder?
Why do custom hooks have to be named with a "use" prefix?
Ask Aria about Project Structure and Conventions
Your personal AI tutor — ask anything about this concept
Revision Status
Personal Notes
Sign in to save personal notes for this topic.
Discussion
Sign in to join the discussion.