Skip to content

React Tutorial Routing and Application Structure

Feature-Based Project Structure for React Apps

Lesson 112 of 156 2 min read By Deepak Paswan Updated Oct 10, 2026

Full-project lesson · needs Node.js, npm and real project files, so it does not run in the in-page playground.

Grouping by kind (all components together, all hooks together) works at first. As an app grows to dozens of screens, one change touches files in five folders, and nobody knows which components belong to what. Feature-based structure groups by what the code is for.

The idea

A feature is a slice of the product that users recognise: products, cart, checkout, account, search. Each feature folder holds everything for that slice: its components, hooks, API calls, utilities and tests.

code
PLAIN
src/
├── app/                      app setup: router, providers, layout
│   ├── App.jsx
│   └── routes.jsx
├── features/
│   ├── products/
│   │   ├── components/
│   │   │   ├── ProductList.jsx
│   │   │   └── ProductCard.jsx
│   │   ├── api.js
│   │   ├── useProducts.js
│   │   ├── ProductsPage.jsx
│   │   └── index.js          the feature's public API
│   ├── cart/
│   │   └── ...
│   └── account/
│       └── ...
├── shared/                   code used by several features
│   ├── components/           Button, Modal, Layout
│   ├── hooks/
│   └── utils/
└── main.jsx

Why it helps

  • Change locality. A task like “add a discount to the cart” touches features/cart only.
  • Easy to find. New developers look for the feature, not for a kind of file.
  • Easy to delete. Removing a feature means removing a folder.
  • Clear ownership. Teams can own features.
  • Encourages boundaries. A feature exposes a few things and hides the rest.

The public API of a feature

Each feature has an index.js that exports only what other parts of the app may use:

src/features/products/index.js
JS
export { default as ProductsPage } from './ProductsPage.jsx'
export { useProducts } from './useProducts.js'

Other code imports from the feature folder, not from its inside:

App.jsx
JSX
import { ProductsPage } from '../features/products'   // good
import ProductCard from '../features/products/components/ProductCard.jsx'   // reaching inside: avoid

That keeps internals free to change.

Dependency rules

Without rules, features tangle together. A simple set:

  1. shared imports from nothing in features or app.
  2. A feature may import from shared and, sparingly, from another feature’s public API.
  3. app imports from features and shared to assemble the application.
  4. Features do not import each other’s internals, and avoid cycles.

If two features keep needing the same code, move it to shared (or merge the features).

A verified mini example

This structure was built with Vite and tested in a browser: the app shell wires a route to a feature through its public API.

src/features/products/useProducts.js
JS
import { useState } from 'react'

const INITIAL = [
  { id: 1, name: 'Keyboard' },
  { id: 2, name: 'Mouse' },
]

export function useProducts() {
  const [items] = useState(INITIAL)
  return { products: items }
}
src/features/products/components/ProductCard.jsx
JSX
function ProductCard({ name }) {
  return <li>{name}</li>
}

export default ProductCard
src/features/products/ProductsPage.jsx
JSX
import ProductCard from './components/ProductCard.jsx'
import { useProducts } from './useProducts.js'

function ProductsPage() {
  const { products } = useProducts()
  return (
    <section>
      <h1>Products feature</h1>
      <ul>
        {products.map((p) => <ProductCard key={p.id} name={p.name} />)}
      </ul>
    </section>
  )
}

export default ProductsPage

(src/features/products/index.js is the file shown above.) Now the app shell:

src/app/routes.jsx
JSX
import { Routes, Route, Link } from 'react-router'
import { ProductsPage } from '../features/products'

function AppRoutes() {
  return (
    <div>
      <nav><Link to="/">Home</Link> | <Link to="/products">Products</Link></nav>
      <Routes>
        <Route path="/" element={<h1>Welcome</h1>} />
        <Route path="/products" element={<ProductsPage />} />
      </Routes>
    </div>
  )
}

export default AppRoutes
src/App.jsx
JSX
import { BrowserRouter } from 'react-router'
import AppRoutes from './app/routes.jsx'

function App() {
  return (
    <BrowserRouter>
      <AppRoutes />
    </BrowserRouter>
  )
}

export default App

When to switch

Do not start a three-page app with five levels of folders. A reasonable path:

  1. Start flat or grouped by kind (Organizing Components).
  2. When a folder has too many unrelated files, or work keeps crossing folders, extract the first feature.
  3. Move features one at a time; the app keeps running throughout.

There is no single correct structure. Pick one, write it down for the team and keep it consistent. Many teams use a mix: feature folders for the product, and a shared folder for the building blocks.

Key points

  • Group code by feature so related files live together and changes stay local.
  • Give each feature a small public index.js and avoid reaching into other features’ internals.
  • Keep dependencies one-directional: shared knows nothing of features, features know shared, app knows both.