Full-project lesson · needs Node.js, npm and real project files, so it does not run in the in-page playground.
A folder structure cannot make code good, but a clear one saves time every day: new files have an obvious home, and old ones are easy to find. This lesson gives you simple rules that work for small and medium apps. Feature-Based Project Structure shows the next step for bigger ones.
Rule 1: one main component per file
Name the file after the component, in PascalCase:
Button.jsx exports Button
ProductCard.jsx exports ProductCard
Small helper components used only by one parent may share its file. If a helper grows, move it out.
Rule 2: group by kind (a good start)
A beginner-friendly layout groups files by what they are:
src/
├── main.jsx
├── App.jsx
├── components/ reusable UI pieces (Button, Modal, Card)
│ ├── Button.jsx
│ └── Modal.jsx
├── pages/ one component per route (Home, ProductPage)
│ ├── Home.jsx
│ └── ProductPage.jsx
├── hooks/ custom hooks (useLocalStorage)
│ └── useLocalStorage.js
├── utils/ plain JavaScript helpers (formatPrice)
│ └── formatPrice.js
├── api/ functions that call the server
│ └── products.js
└── assets/ images, fonts
| Folder | Holds | Knows about routes or data? |
|---|---|---|
components | Generic, reusable UI | No: gets everything through props |
pages (or routes) | The screen for each route | Yes: reads params, loads data, composes components |
hooks | Reusable stateful logic | Sometimes |
utils | Pure functions, no React | No |
api | Network calls | Yes: URLs and data shapes |
The key distinction is page vs component. A page is wired to the router and the data. A component just displays what it receives.
Rule 3: keep related files together
A component’s styles, tests and small private parts can sit next to it:
components/
└── Button/
├── Button.jsx
├── Button.module.css
├── Button.test.jsx
└── index.js // export { default } from './Button.jsx'
An index.js file lets you write import Button from './components/Button' instead of the longer path. Use this for components that have several files; for single files it only adds noise.
Rule 4: import carefully
Relative paths such as ../../../components/Button are hard to read and break when files move. Vite can define an alias, for example @ for src:
import path from 'node:path'
import react from '@vitejs/plugin-react'
import { defineConfig } from 'vite'
export default defineConfig({
plugins: [react()],
resolve: {
alias: { '@': path.resolve(import.meta.dirname, 'src') },
},
})
import Button from '@/components/Button.jsx'
(With TypeScript, mirror the alias in tsconfig under paths.)
Rule 5: avoid cycles
If a.jsx imports b.jsx and b.jsx imports a.jsx, you get a circular dependency, which causes confusing “undefined” errors. Keep imports flowing one way: pages import components; components import utils; components should not import pages.
Rule 6: name things well
- Components: nouns,
ProductCard,CheckoutForm. - Hooks: start with
use,useCart. - Handlers:
handleClick; handler props:onClick. - Event and data files:
products.js,formatPrice.js. - Booleans:
isOpen,hasError.
Rule 7: do not over-split or over-plan
- Do not create 30 folders for 5 files. Start flat and add structure when folders get crowded (roughly more than ten files).
- Do not make a component “reusable” before you have a second use. Copy once, extract at the third time.
- Moving files is cheap with an editor that updates imports, so improve the layout as you learn.
A small, verified example
This layout was built with Vite and tested in a browser. A page imports a reusable component, and a utility formats the price.
export function formatPrice(cents) {
return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(cents / 100)
}
import { formatPrice } from '../utils/formatPrice.js'
function ProductCard({ name, priceCents }) {
return (
<article>
<h2>{name}</h2>
<p>{formatPrice(priceCents)}</p>
</article>
)
}
export default ProductCard
import ProductCard from '../components/ProductCard.jsx'
function Home() {
return (
<main>
<h1>Shop</h1>
<ProductCard name="Keyboard" priceCents={4950} />
</main>
)
}
export default Home
import Home from './pages/Home.jsx'
function App() {
return <Home />
}
export default App
Key points
- One main component per file, named after it; group by kind to start with.
- Keep pages (routed, data-aware) apart from components (reusable, props-driven).
- Use import aliases, avoid circular imports and add structure only when you need it.