DEV Community

Cover image for React Mastery Series – Day 30: React Project Folder Structure & Scalable Architecture
Siva Samanthapudi
Siva Samanthapudi

Posted on

React Mastery Series – Day 30: React Project Folder Structure & Scalable Architecture

Welcome back to the React Mastery Series! 🎉

We've officially reached Day 30 of this journey.

So far, we've covered everything from React fundamentals to advanced topics like:

  • Hooks
  • Context API
  • Redux Toolkit
  • API Integration
  • Authentication
  • Performance Optimization
  • Testing
  • Error Handling
  • Design Patterns

Today, we'll learn how to organize React projects like experienced engineering teams at enterprise companies.

Why Project Structure Matters

As your application grows, so does its complexity.

A project with 10 files is easy to navigate.

A project with 5,000+ files is not.

Without a proper structure, developers often struggle with:

  • Finding files
  • Reusing components
  • Managing dependencies
  • Scaling new features
  • Onboarding new team members

A well-organized architecture solves these problems.


Small Project Structure

A simple React project might look like this:

src
├── App.tsx
├── main.tsx
├── components
├── pages
├── hooks
├── utils
└── assets
Enter fullscreen mode Exit fullscreen mode

This works well for:

  • Learning React
  • Personal projects
  • Small business applications

However, it becomes difficult to manage as the application grows.


The Problem with Layer-Based Structure

Many beginners organize code like this:

src
├── components
├── pages
├── services
├── hooks
├── reducers
├── utils
├── types
└── styles
Enter fullscreen mode Exit fullscreen mode

At first glance, this looks clean.

But imagine an enterprise banking application with:

  • 300 components
  • 100 services
  • 150 hooks

Finding files becomes increasingly difficult.


Feature-Based Architecture

Modern React applications organize code by business features, not by file type.

Example:

src
├── features
│   ├── auth
│   ├── accounts
│   ├── payments
│   ├── dashboard
│   ├── notifications
│   └── investments
Enter fullscreen mode Exit fullscreen mode

Everything related to a feature lives together.

This improves maintainability and team collaboration.


Inside a Feature

Let's look at the Accounts feature.

accounts
├── api
├── components
├── hooks
├── pages
├── services
├── store
├── types
├── utils
└── index.ts
Enter fullscreen mode Exit fullscreen mode

Each feature becomes a self-contained module.

Benefits:

  • Easier navigation
  • Better encapsulation
  • Simpler refactoring

Shared Folder

Some code is reused across multiple features.

Store it in a shared directory.

src
├── shared
│   ├── components
│   ├── hooks
│   ├── services
│   ├── constants
│   ├── utils
│   └── types
Enter fullscreen mode Exit fullscreen mode

Examples include:

  • Buttons
  • Modal components
  • Date utilities
  • API clients
  • Common TypeScript types

API Layer

Avoid calling APIs directly inside components.

❌ Bad:

function Dashboard() {
  useEffect(() => {
    axios.get("/accounts");
  }, []);

  return <div>Dashboard</div>;
}
Enter fullscreen mode Exit fullscreen mode

✅ Better:

accounts
└── services
    └── accountService.ts
Enter fullscreen mode Exit fullscreen mode
import { api } from "@/shared/services/api";

export async function getAccounts() {
  const response = await api.get("/accounts");

  return response.data;
}
Enter fullscreen mode Exit fullscreen mode

Component:

const accounts = await getAccounts();
Enter fullscreen mode Exit fullscreen mode

Business logic remains outside the UI.


Component Organization

Separate reusable UI from feature-specific components.

shared
└── components
    ├── Button
    ├── Modal
    ├── Loader
    └── Input
Enter fullscreen mode Exit fullscreen mode

Feature-specific components:

```text id="1tfnvo"
accounts
└── components
├── AccountCard
├── BalanceChart
└── TransactionTable




This keeps the shared library small and meaningful.

---

# Custom Hooks

Shared hooks:



```text
shared
└── hooks
    ├── useDebounce.ts
    ├── useLocalStorage.ts
    └── useWindowSize.ts
Enter fullscreen mode Exit fullscreen mode

Feature hooks:

accounts
└── hooks
    ├── useAccounts.ts
    └── useTransactions.ts
Enter fullscreen mode Exit fullscreen mode

Hooks should live as close as possible to the feature they support.


State Management

Using Redux Toolkit?

Organize slices by feature.

features
├── auth
│   └── authSlice.ts
├── accounts
│   └── accountSlice.ts
└── payments
    └── paymentSlice.ts
Enter fullscreen mode Exit fullscreen mode

Root store:

src
└── app
    └── store.ts
Enter fullscreen mode Exit fullscreen mode

Each feature owns its own state.


Routing Structure

Instead of placing all routes in one file:

  App.tsx
     ↓
200 Routes
Enter fullscreen mode Exit fullscreen mode

Split routes by feature.

src
├── routes
│   ├── AppRoutes.tsx
│   ├── AuthRoutes.tsx
│   └── AdminRoutes.tsx
Enter fullscreen mode Exit fullscreen mode

This makes routing easier to maintain.


Assets Organization

Keep assets organized.

src
├── assets
│   ├── images
│   ├── icons
│   ├── fonts
│   └── styles
Enter fullscreen mode Exit fullscreen mode

Avoid scattering images throughout the project.


Environment Configuration

Never hardcode URLs or secrets.

❌ Bad:

const apiUrl = "https://production-api.com";
Enter fullscreen mode Exit fullscreen mode

✅ Better:

.env

```text id="h51qxc"
VITE_API_URL=https://api.example.com




Usage:



```tsx 
const apiUrl = import.meta.env.VITE_API_URL;
Enter fullscreen mode Exit fullscreen mode

This allows different configurations for development, testing, and production.


Barrel Exports

Instead of:

import Button from "./Button";
import Input from "./Input";
import Modal from "./Modal";
Enter fullscreen mode Exit fullscreen mode

Create an index.ts.

export { default as Button } from "./Button";
export { default as Input } from "./Input";
export { default as Modal } from "./Modal";
Enter fullscreen mode Exit fullscreen mode

Now import everything from one place.

import { Button, Input, Modal } from "@/shared/components";
Enter fullscreen mode Exit fullscreen mode

Cleaner imports improve readability.


Example Enterprise Folder Structure

src
├── app
│   ├── store.ts
│   └── providers.tsx
├── assets
├── features
│   ├── auth
│   ├── dashboard
│   ├── accounts
│   ├── payments
│   ├── transactions
│   └── investments
├── routes
├── shared
│   ├── components
│   ├── hooks
│   ├── services
│   ├── utils
│   ├── constants
│   └── types
├── layouts
├── styles
├── App.tsx
└── main.tsx
Enter fullscreen mode Exit fullscreen mode

This structure scales well for teams working on large applications.


Monorepo Architecture

As organizations grow, multiple applications often share code.

Example:

apps
├── customer-portal
├── admin-portal
└── mobile-web

packages
├── ui
├── auth
├── api
└── utils
Enter fullscreen mode Exit fullscreen mode

Tools such as Nx and Turborepo make it easier to manage monorepos.


Real-World Banking Example

Imagine a digital banking platform.

features
├── accounts
├── cards
├── loans
├── payments
├── investments
├── support
└── profile
Enter fullscreen mode Exit fullscreen mode

Each team owns one or more features.

They can build, test, and deploy changes independently while following the same architectural standards.


Common Mistakes

1. Organizing Everything by File Type

Large components or services folders quickly become difficult to navigate.

Group files by feature instead.


2. Creating a Shared Folder Too Early

Not every component belongs in shared.

Only move components there when they're genuinely reused across features.


3. Mixing Business Logic with UI

Keep:

  • API calls
  • Validation
  • Data transformations

outside your presentation components.


4. Ignoring Naming Conventions

Be consistent with:

  • Folder names
  • File names
  • Component names
  • Hook names

Consistency improves readability across the team.


Best Practices

  • Organize by feature, not by file type.
  • Keep features self-contained.
  • Separate UI, business logic, and API layers.
  • Create shared modules only when reuse is proven.
  • Use barrel exports for cleaner imports.
  • Keep environment-specific configuration outside source code.
  • Follow consistent naming conventions throughout the project.

Key Takeaways

Today, we learned:

✅ Feature-based architecture scales better than layer-based organization.
✅ Shared components should contain only reusable code.
✅ Business logic belongs in services and hooks, not UI components.
✅ Redux slices should be organized by feature.
✅ Environment variables simplify configuration management.
✅ A well-structured project improves collaboration and long-term maintainability.


🎉 Milestone Reached!

Congratulations on completing 30 articles in the React Mastery Series!

By now, you've built a strong foundation in React and explored many of the patterns and practices used in production applications.


Coming Next 🚀

In Day 31, we'll begin a new advanced chapter:

Advanced React Patterns & Enterprise Architecture – Building Large-Scale Applications

We'll cover:

  • Feature-Driven Design (FDD)
  • Domain-Driven Folder Organization
  • Scalable State Management
  • Dependency Injection Concepts
  • Clean Architecture in React
  • Modular Frontend Design
  • Enterprise project case study

This marks the transition from becoming a React developer to thinking like a Senior Frontend Engineer or React Architect.

Happy Coding! 🚀

Top comments (0)