How to structure a React app in 2026

How to structure a React app in 2026

Structuring a React application has never been a solved problem.

Every few years new patterns emerge, old ones get rebranded, and teams reinvent folder structures trying to balance scalability, maintainability, and developer experience. This of course following trends that born anew, evolve or dies month after month.

Whether you’re building a scrappy startup MVP or a massive enterprise dashboard, the basic idea is that your structure should minimize cognitive load and allows the development team to work with minimal context switch (you know, all those VSCode tabs open).

In my honest opinion, which folder structure to choose isn't only about which one is “best”, but about making intentional trade-offs based on how your application evolves over time.

In this article, I’ll walk through the most common approaches, where they shine, where they break, and what actually works in real-world projects.

Folders by type

This is the "Old Reliable" of the React world. You’ve seen it a thousand times: /components, /hooks, /services, and /utils.

  • Pros: It’s incredibly easy for beginners to understand.

  • Cons: It scales poorly. Once you hit 50+ components (actually even less), finding the UserAvatar component while looking at the useUser hook involves a lot of scrolling and mental context switch.

This of course doesn't mean this approach is bad: it's actually a very reasonable structure for very small projects or shared internal libraries where the scope is strictly limited.

A typical React app structured by type would look like the following:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
src/
├── components/
│   ├── Button/
│   │   ├── Button.tsx
│   │   ├── Button.module.css
│   │   └── Button.test.tsx
│   ├── Header/
│   └── Modal/
│
├── pages/            # or routes/ (Next.js / React Router)
│   ├── Home/
│   │   ├── Home.tsx
│   │   └── Home.module.css
│   ├── Blog/
│   └── About/
│
├── hooks/
│   ├── useAuth.ts
│   ├── useFetch.ts
│   └── useDebounce.ts
│
├── services/         # API calls, external services
│   ├── api.ts
│   ├── authService.ts
│   └── postService.ts
│
├── utils/
│   ├── formatDate.ts
│   ├── constants.ts
│   └── helpers.ts
│
├── store/            # global state (if any)
│   ├── index.ts
│   └── slices/
│
├── styles/
│   ├── globals.css
│   └── variables.css
│
├── assets/
│   ├── images/
│   └── icons/
│
├── types/
│   └── index.ts
│
└── main.tsx / index.tsx

Atomic Design

Popularized by Brad Frost, Atomic Design breaks UI down into Atoms, Molecules, Organisms, Templates, and Pages.

Atomic Design in Figma

While mathematically beautiful and a heck of an idea, it often feels like overkill for logic-heavy applications.

However - and this is important - it remains the gold standard for Design Systems: it's everywhere in any component library, and if you are building a UI kit that will be used across multiple apps, Atomic Design provides a clear contract for how components should be composed.

Just be prepared for the eternal debate: "Is a SearchBar a Molecule or an Organism?" (Spoiler: It doesn't really matter as long as you're consistent).

Here’s a typical Atomic Design folder structure applied to a React app:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
src/
├── components/
│   ├── atoms/
│   │   ├── Button/
│   │   │   ├── Button.tsx
│   │   │   ├── Button.module.css
│   │   │   ├── Button.test.tsx
│   │   │   └── Button.stories.tsx
│   │   ├── Input/
│   │   ├── Label/
│   │   └── Icon/
│   │
│   ├── molecules/
│   │   ├── SearchBar/
│   │   │   ├── SearchBar.tsx
│   │   │   ├── SearchBar.module.css
│   │   │   └── SearchBar.stories.tsx
│   │   ├── FormField/
│   │   └── Dropdown/
│   │
│   ├── organisms/
│   │   ├── Header/
│   │   │   ├── Header.tsx
│   │   │   └── Header.module.css
│   │   ├── Sidebar/
│   │   └── PostList/
│   │
│   ├── templates/
│   │   ├── MainLayout/
│   │   │   ├── MainLayout.tsx
│   │   │   └── MainLayout.module.css
│   │   └── BlogLayout/
│   │
│   └── pages/
│       ├── HomePage.tsx
│       ├── BlogPage.tsx
│       └── AboutPage.tsx
│
├── hooks/
│   ├── useAuth.ts
│   └── usePosts.ts
│
├── services/
│   ├── api.ts
│   └── postService.ts
│
├── utils/
│   └── helpers.ts
│
├── styles/
│   ├── globals.css
│   └── variables.css
│
├── assets/
│   ├── images/
│   └── icons/
│
└── main.tsx

Feature-Based

In 2026, Feature-Based architecture is the undisputed heavyweight champion for production apps: instead of grouping by what a file is (a hook, a component), you group by what it does (Auth, Billing, Search).

A typical feature-based directory structure might look like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
src/
├── app/                      # app-level setup (routing, providers, layout)
│   ├── router.tsx
│   ├── providers.tsx
│   └── layout.tsx
│
├── features/
│   ├── auth/
│   │   ├── components/
│   │   │   ├── LoginForm.tsx
│   │   │   └── SignupForm.tsx
│   │   ├── hooks/
│   │   │   └── useAuth.ts
│   │   ├── api/
│   │   │   ├── login.ts
│   │   │   └── signup.ts
│   │   ├── store/
│   │   │   └── authStore.ts
│   │   ├── types/
│   │   │   └── auth.types.ts
│   │   ├── utils/
│   │   │   └── validators.ts
│   │   └── index.ts         # public entry-point of the feature
│   │
│   ├── posts/
│   │   ├── components/
│   │   │   ├── PostCard.tsx
│   │   │   ├── PostList.tsx
│   │   │   └── PostContent.tsx
│   │   ├── hooks/
│   │   │   ├── usePosts.ts
│   │   │   └── usePost.ts
│   │   ├── api/
│   │   │   ├── getPosts.ts
│   │   │   └── getPost.ts
│   │   ├── types/
│   │   │   └── post.types.ts
│   │   ├── utils/
│   │   │   └── formatPost.ts
│   │   └── index.ts
│   │
│   └── ui/                  # or often "core" - contains UI primitives
│       ├── components/
│       │   ├── Button.tsx
│       │   ├── Input.tsx
│       │   └── Spinner.tsx
│       └── index.ts
│
├── shared/                  # truly global/shared logic
│   ├── lib/
│   │   ├── apiClient.ts
│   │   └── config.ts
│   ├── hooks/
│   │   └── useDebounce.ts
│   ├── utils/
│   │   └── helpers.ts
│   └── types/
│       └── global.types.ts
│
├── assets/
│   ├── images/
│   └── icons/
│
├── styles/
│   └── globals.css
│
└── main.tsx


This approach enhances maintainability, scalability, and developer experience in medium-to-large projects.

It bringing the following important benefits:

  • Minimize Context Switching and Cognitive Load:
    All files relating to a feature (components, hooks, services, types) live together. This minimizes jumping between folders, accelerating development and reducing cognitive load.

  • Improved Scalability:
    Adding new features simply means creating a new folder, preventing top-level folders from becoming bloated.

  • Enhanced Maintainability and Encapsulation:
    Features can be developed, tested, and refactored in isolation. It encourages clearer, restricted dependencies between features, reducing accidental side effects

  • Team-Friendly Organization:
    Teams can own specific feature directories, reducing merge conflicts and enabling parallel development.

  • Easier Onboarding:
    New developers can look at the file structure and instantly understand the business domains of the application.

  • Better Code Splitting/Performance:
    It is easier to implement lazy loading or code splitting on a feature-by-feature basis.

Hexagonal Architecture

Also known as "Ports and Adapters", this approach has migrated from the backend to complex React frontends. The goal is to decouple your business logic from the UI framework.

In this world, your core logic lives in a "Domain" layer that doesn't know React exists. If you decided to swap React for a different framework tomorrow (unlikely, but stay with me), your core business rules wouldn't change. It’s heavy on boilerplate but essential for apps with massive, complex client-side logic that needs to be unit-tested in isolation.

Here an example of Hexagonal Architecture, just to understand what I mean:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
src/
├── app/                         # app setup (routing, providers, layout)
│   ├── router.tsx
│   ├── providers.tsx
│   └── layout.tsx
│
├── features/
│   └── posts/
│       ├── domain/              # core business logic (framework-agnostic)
│       │   ├── entities/
│       │   │   └── Post.ts
│       │   ├── value-objects/
│       │   │   └── PostTitle.ts
│       │   ├── repositories/    # interfaces (ports)
│       │   │   └── PostRepository.ts
│       │   └── use-cases/
│       │       ├── getPosts.ts
│       │       └── getPost.ts
│       │
│       ├── application/         # orchestration layer (optional)
│       │   └── postService.ts
│       │
│       ├── infrastructure/      # external implementations (adapters)
│       │   ├── api/
│       │   │   └── postApi.ts
│       │   ├── repositories/
│       │   │   └── PostRepositoryImpl.ts
│       │   └── mappers/
│       │       └── postMapper.ts
│       │
│       ├── ui/                  # React-specific layer
│       │   ├── components/
│       │   │   ├── PostCard.tsx
│       │   │   └── PostList.tsx
│       │   ├── hooks/
│       │   │   ├── usePosts.ts
│       │   │   └── usePost.ts
│       │   └── pages/
│       │       └── BlogPage.tsx
│       │
│       └── index.ts             # public API
│
├── shared/
│   ├── lib/
│   │   └── httpClient.ts
│   ├── types/
│   └── utils/
│
└── main.tsx

Major benefits of using an Hexagonal Architecture

  • High Testability:
    Business logic can be tested in isolation without needing a database or UI, making tests faster and more reliable.

  • Improved Maintainability & Decoupling:
    Core logic is agnostic to external technologies (e.g., databases, API frameworks). Switching or upgrading components—such as moving from MySQL to MongoDB—requires only changing the adapter, not the core business logic.

  • Technology Flexibility:
    The "plug-in" approach allows for easily swapping technologies or changing how the system is invoked (e.g., CLI, HTTP, or Messaging) without affecting core functionality.

  • Clear Separation of Concerns:
    Clearly separates domain logic from infrastructure, enhancing modularity and aligning well with DDD (Domain-Driven Design).

Cons of Hexagonal Architecture

  • Increased Complexity and Overengineering:
    It introduces many interfaces and adapter classes. For simple, small, or CRUD-heavy projects, this can be significant overengineering.

  • Higher Initial Effort:
    The initial design takes more time and effort to implement ports and adapters properly.

  • Learning Curve:
    Teams unfamiliar with the architectural style may struggle to understand the separation between the domain, application, and adapter layers.

  • Potential Performance Overhead:
    While rarely a bottleneck, the abstraction layers can introduce minor performance costs.

When to Use It

  • Complex systems where business logic changes independently of the technology stack.

  • Projects that require high maintainability, testability, and flexibility over time.

  • Domain-Driven Design projects.

Other things to consider

Component exports

The industry has largely moved toward Named Exports over Default Exports. Why?

  • Refactoring: Renaming a named export updates all references automatically.

  • Autocompletion: Your IDE (and AI assistants) can find named exports much faster.

  • Index files: Use index.ts files as "Barrels" to export only what’s necessary from a folder, keeping your internal implementation details private.

I have to admit, however, that I still use default exports in my own projects when a file has only one export: it just feels to me more logical.

API layer

With the maturity of Server Actions and data-fetching libraries like TanStack Query, the days of scattering useEffect fetches across components are over.

  • Centralize your API calls: Keep them in a dedicated api/ or services/ folder within your features. Structure it based on your domain (e.g. posts.api.ts, auth.api.ts) rather than generic files.

  • Keep components unaware of data sources: UI components shouldn’t care whether data comes from REST, GraphQL, or Server Actions. Always access data through hooks or use-case functions that act as a boundary.

Tests organization

Co-location is king. Stop putting your tests in a giant __tests__ folder at the very app root or, worse, in a __tests__ folder at each feature base . I've done it, and I reggretted it immediately, as it looks ugly, unpractical, and just more difficult to follow.

Put UserCard.test.tsx right next to UserCard.tsx. This makes it immediately obvious which components lack coverage and ensures that when you move or delete a component, the test goes with it.

Storybook and Styles

  • Storybook: Treat it as your living documentation. Like tests, Storybook files should be co-located with their components.

  • Styles: Tailwind CSS remains the dominant force for its "design system in a box" utility. However, for those using CSS Modules or CSS-in-JS, keeping the styles in the same folder as the component is non-negotiable.

Conclusions

As you might have noticed, there is no "perfect" React folder structure — only the one that helps your team move fastest with the fewest bugs.

If you're unsure, start with a Feature-Based approach. It offers the best balance of scalability and simplicity.

And remember: your folder structure is not a permanent contract. It should evolve as your application grows, so don't be afraid to start with what makes sense and evolve it over time.