Where Things Live in a Real React App
Project Structure
There's no one "correct" layout, but a few conventions show up again and again. Pick one and stay consistent.
What you'll learn
- Recognize common folder layouts
- Know where to put components, hooks, utils, types
- Avoid premature folder splitting
There’s no React-enforced project layout. A few conventions show up in most codebases — pick one and stick with it.
A Small App
For a starter Vite app:
src/
components/
Button.jsx
Card.jsx
pages/
Home.jsx
Settings.jsx
hooks/
useAuth.js
lib/
api.js
App.jsx
main.jsx components/— reusable UI piecespages/(orroutes/) — top-level screenshooks/— custom hookslib/(orutils/) — plain JS helpersApp.jsx— top-level component, routing setupmain.jsx— the React entry point
A Bigger App — Feature Folders
For larger apps, group by feature instead of type:
src/
features/
auth/
LoginForm.jsx
useAuth.js
authApi.js
todos/
TodoList.jsx
TodoItem.jsx
useTodos.js
todosApi.js
shared/
components/ ← components used across features
hooks/
lib/
pages/
App.jsx The win: when you delete a feature, you delete one folder. Files that change together live together.
File Naming
Common conventions:
- Components:
PascalCase.jsx—UserCard.jsx - Hooks:
useCamelCase.js—useFetch.js - Helpers:
camelCase.js—formatDate.js
Pick a casing and stay consistent. A folder full of mismatched files is its own kind of mess.
When to Split a File
Default to one component per file. Exception: tiny helper components that only one parent uses can share a file with their parent.
// In Card.jsx
function CardImage({ src }) { return <img className="card__img" src={src} />; }
function CardTitle({ children }) { return <h3 className="card__title">{children}</h3>; }
export function Card({ src, title, body }) {
return (
<article className="card">
<CardImage src={src} />
<CardTitle>{title}</CardTitle>
<p>{body}</p>
</article>
);
} The inner helpers are unexported — only Card uses them.
Don’t Premature-Optimize Folders
Three components don’t need three folders. Start flat; split when the listing gets long enough to be annoying.
Path Aliases
Add an alias in vite.config.js so deep imports look clean:
// vite.config.js
import path from "node:path";
export default {
resolve: {
alias: { "@": path.resolve(__dirname, "src") },
},
}; Now import Button from "@/components/Button" instead of
../../components/Button.
Up Next
Real apps have routes. Let’s talk routing.
Routing Intro →