Every React project starts tidy. A few months and thirty components later, nobody remembers where anything lives. A good structure is less about following a famous pattern and more about answering one question quickly: where does this code belong?
Organize by responsibility first
A simple, durable starting point separates code by what it does:
src/
pages/ one file per route
components/ reusable UI, grouped by feature
data/ static content and mock data
services/ data access (API, database)
hooks/ shared custom hooks
utils/ pure helper functions
layouts/ page shells (header, footer)
Pages are thin
A page component should assemble things, not implement them. It fetches data, handles the loading and error states, and composes components. If a page grows past a couple of hundred lines, extract the parts into components named after what they show.
Keep data access behind a service
Components should not know whether articles come from a JSON file, a REST API or a database. Put all reads and writes in a small service module that returns plain objects. When the data source changes, only that module changes.
// services/contentRepository.js
export async function getPostBySlug(slug) {
return posts.find((p) => p.slug === slug) ?? null;
}
Colocate what changes together
Group components by feature (
blog/,projects/) rather than by type (buttons/,cards/).Put truly generic pieces such as buttons, inputs and modals in
components/ui/.Keep a component, its small helpers and its tests near each other.
Rules of thumb
Extract a component when you copy markup a second time, not before.
Extract a hook when stateful logic is shared or makes a component hard to read.
Keep pure functions (formatting, slugs, filtering) in
utils/so they are trivial to test.Prefer plain props and local state; add global state only when two distant parts genuinely need it.
Refactor on a schedule
Structure drifts. A short review every few weeks, asking what felt hard to find, keeps the project navigable without a big rewrite.