Back to Blog
Lesson 31 of the React Fundamentals: Build Modern UIs from Scratch course
ReactJune 25, 20264 min read

Folder Structure Best Practices for Scalable React Apps

Master React project structure to keep your codebase organized. Learn how to separate components, hooks, and services to ensure long-term scalability.

reactjavascriptfrontend

Previously in this course, we explored Refactoring for Clean Code: Improving React Maintainability, where we focused on cleaning up individual component logic. Now that your code is cleaner, it's time to ensure your project's physical layout supports that cleanliness as your app grows.

A disorganized project structure is the silent killer of productivity. When you spend five minutes hunting for a single file, you lose focus. By following consistent folder organization, you build a foundation that scales alongside your features.

Why Project Structure Matters for Scalability

As your movie-browser app grows from a few components to dozens, dumping everything into a single src folder becomes unmanageable. A professional project structure provides a "mental map" for your codebase. When a new developer (or your future self) joins the project, they should be able to intuit where a specific piece of logic lives without reading every file.

Scalability isn't just about handling more data; it's about handling more complexity without introducing bugs. Modularizing your project helps you isolate concerns, making it easier to test components and swap out implementations.

The Professional Directory Layout

For most React applications, I recommend a structure that groups files by responsibility rather than by feature initially, as it provides a clear separation of concerns.

Here is the standard layout we will adopt for our movie-browser app:

TEXT
src/
├── components/       # Reusable UI building blocks (e.g., MovieCard, SearchBar)
├── hooks/            # Custom React hooks (e.g., useFetch, useLocalStorage)
├── services/         # API calls and external data fetching logic
├── utils/            # Helper functions (e.g., date formatters, API constants)
├── assets/           # Images, fonts, or global CSS
├── App.jsx           # Root component
└── main.jsx          # Entry point

1. Components

This folder contains your presentational components. If a component is specific to a single page, you might create a sub-folder: components/movie/MovieCard.jsx. Keep this folder strictly for UI logic.

2. Hooks

When you extract logic from your components (like we discussed in our Refactoring for Clean Code lesson), move those reusable functions here. Prefix them with use, such as useMovieSearch.js.

3. Services

Never put API fetching logic directly inside a useEffect if you can avoid it. Instead, create a service file like services/movieService.js that exports functions like fetchMovies(query). This keeps your components focused on rendering, not network protocol details.

Worked Example: Moving Logic to Services

Let's refactor a part of our movie-browser. Previously, we might have had our fetch call sitting inside a useEffect in our App.jsx. That is a "leaky abstraction."

Before:

JSX
// App.jsx - Too much going on!
useEffect(() => {
  fetch(CE9178">`https://api.example.com/movies?q=${query}`)
    .then(res => res.json())
    .then(data => setMovies(data));
}, [query]);

After: First, create src/services/movieService.js:

JAVASCRIPT
export const getMovies = async (query) => {
  const response = await fetch(CE9178">`https://api.example.com/movies?q=${query}`);
  if (!response.ok) throw new Error("Failed to fetch");
  return response.json();
};

Then, import it into your component:

JSX
import { getMovies } from CE9178">'./services/movieService';

// Inside your component
useEffect(() => {
  getMovies(query).then(setMovies);
}, [query]);

Hands-on Exercise

  1. Create a services directory inside your src folder.
  2. Move your existing API fetch logic from your main component into a new file named movieService.js within that directory.
  3. Update your component imports to point to this new service.
  4. Verify your app still fetches and renders movies correctly.

Common Pitfalls

  • Deep Nesting: Don't go deeper than 3-4 levels. If you find yourself in components/ui/buttons/primary/large/, you've over-engineered it.
  • Mixing Logic and UI: Avoid putting API logic in your component files. Keep components "dumb" and services "smart."
  • Naming Inconsistency: Pick a convention and stick to it. I prefer PascalCase for components (MovieCard.jsx) and camelCase for hooks and services (useFetch.js, movieService.js).
  • Ignoring the utils folder: If you have a function that formats dates or cleans strings, don't leave it inside a component. Put it in utils/formatters.js so it can be tested in isolation.

Recap

A clean project structure is the difference between a project that's a joy to work on and one that feels like a chore. By separating your UI components, business logic (services), and reusable hooks, you ensure that your code is modular, testable, and ready for whatever features you add next. Remember: folder organization is a living process; it's okay to move files as your understanding of the app's architecture evolves.

Up next: We will continue our refactoring journey by taking the logic we just moved and turning it into a truly reusable custom hook.

Similar Posts