Skip to content

How to structure your React projects

Every React project looks a bit different. There are so many ways to structure frontend projects. In this article we'll discuss three different project structures.

Published
Feb 16, 2023
Updated
Feb 16, 2023
Reading time
7 min read

Frontend projects are like snowflakes - every project is unique. There is no popular, widespread way of structuring a frontend application. Every React project has its own guidelines. And while some may say that Angular projects are more unified, I question if this is actually true, given that Angular itself provides many ways to work with modules (feature based, SCAM, standalone, ...).

The truth is: the only right way for structuring projects doesn't exist. It depends on your requirements and the tools and frameworks you use. In this article we'll discuss three different project structures.

Structure by type

Structuring files by their type is common in tutorials and small projects. You create separate folders for components, hooks, routes and more.

src/
├── components/
├── hooks/
├── routes/
├── types/
└── utils/

The pattern is simple and easy to apply. But the truth is: it doesn't scale. The structure works fine for very small projects or simple websites. For any real application I don't recommend it.

If you want to learn more, read the article Delightful React File/Directory Structure by Joshua Comeau.

Let's move on to another, better way to structure projects.

Bulletproof React

Bulletproof React is a set of guidelines for React applications. Not just about the project structure, but about styling, linting, testing and more. I want to focus here on the project structure.

The recommended project structure is based on feature modules with some global, shared components and hooks:

src/
├── assets/
├── components/           # Shared Components
├── config/
├── features/             # Feature modules
│   ├── my-feature/
│   │   ├── api/
│   │   ├── assets/
│   │   ├── components/
│   │   ├── hooks/
│   │   ├── routes/
│   │   ├── stores/
│   │   ├── types/
│   │   ├── utils/
│   │   └── index.ts      # Public API
│   └── another-feature/
├── hooks/                # Shared hooks
├── lib/
├── providers/
└── routes/

Most of the code lives inside the features folder, split into separate features. The files inside a feature folder are split by type, like components and hooks. Each feature module has an index.ts file that acts as a public API for other feature modules. Other feature modules are only allowed to use stuff that is exported from the index.ts file.

Components and hooks that are shared across all feature modules live in src/components/ and src/hooks/.

Overall, it's pretty good structure and works for a lot of projects. Working with feature modules helps you to keep your code properly structured into small, easy to understand pieces.

I see a few limitations when using this approach:

  • Structure by type: Structuring files by type (components, hooks, ...) on a lower level (like inside feature modules) works fine most of the time. But sometimes you have components and hooks that are tightly coupled together. You then either have to split them across two folders or violate the guidelines. Neither is a good solution.
  • Dependencies between features: Feature modules have dependencies between each other, that's inevitable. Unfortunately, they are not transparent and don't know on what a feature depends on. If your feature modules are not properly split you may end up having circular dependencies.
  • Global/feature dependencies: Having a look at the example app implemented by Bulletproof React, you will see that global files access feature modules and feature modules access global files. There are no guidelines for these dependencies, and it may get messy if you don't handle this carefully.
  • Community: There is no real community around these guidelines. There are documented and you can use them. But there are no discussions and rarely any improvements on the docs.

Let's move on to the last approach, Feature-sliced design.

Feature-sliced design

Another, probably less known way for structuring projects is Feature-sliced design. It's based on layers, slices and segments.

FSD Schema: Layers, slices and segments

Structure

Let's take a closer look at the structure:

Layers

The top level directories are layers, split by their responsibility. The layers are:

  • app/: Application initialization logic and static assets
  • processes/: Workflows involving multiple pages
  • pages/: Complete application views
  • widgets/: Various combinations of abstract and / or business units from lower layers
  • features/: User scenarios, which usually operate on business entities
  • entities/: Business units in terms of which application business logic works
  • shared/: Reusable non-business specific modules

You may not need all of these layers. For example, if you don't have any workflows involving multiple pages you can omit the processes layer.

Dependencies between layers are strictly limited: Higher layers can depend on lower layers but not vice-versa. In example, the app layer can depend on any other layer, shared cannot depend on other layers and features can depend on entities and shared but not on widgets, pages, processes and app.

These rules not just help to avoid circular dependencies between layers but also make it clear on what a layer may depend on. In addition, it helps to understand the impact of changes inside a layer. Changes in the shared layer may affect many other layers (and are therefore more critical), while changes in the processes layer only affect the app layer.

Slices

Inside the layers you create slices, which partition the code by business domain. Slices are not regulated by the methodology but depend on your project. You may have an entities/post/ slice that represents blog posts or a features/write-comment slice to write comments.

Segments

Each slice is split into one or more of the following segments:

  • ui/: User Interface components and UI related logic
  • model/: Business logic (store, actions, effects, reducers, etc.)
  • lib/: Infrastructure logic (utils/helpers)
  • config/: Local configuration (constants, enums, meta information)
  • api/: Logic of API requests (api instances, requests, etc.)

Comparing

Compared to Bulletproof React, Feature-sliced Design is a bit more complex to understand and apply. But in my opinion it's worthwhile to adapt this structure for a few reasons:

  • Layer dependencies: Layers can depend on each other but only in one direction. This makes dependencies easier to manage and prevents you from running into circular dependency issues.
  • Reusability: The layers and their dependencies encourage reusability. Features on lower layers may be reused on multiple higher layers, like different widgets or pages. In other methodologies, to make code reusable you either have to move code into global folders or extract them into feature modules that aren't features but just exist for reusability purposes only.
  • Composition: The methodology encourages component composition. Higher levels like widgets compose their UI based on elements from lower layers like features and entities.
  • No separation by type: You don't split your code by type, i.e. you don't have components and hooks folders. This allows you to colocate code that belongs together.
  • High cohesion: You may argue that the code for a page is cluttered around the whole code base because of the layers. It's true that the code for a page is distributed across the layers, but that's for good reason. Not just for reusability and composition, as mentioned above, but it results in slices with high cohesion.
  • Community: Feature-sliced design has an active community that will help you to adopt the methodology. It's mainly a Russian community, but you can ask questions in English too.

Summary

We discussed three different ways for structuring your frontend projects. Structuring by type works well for small, simple projects. Bullet-proof React provides a good project structure, but you can scale much better using Feature-sliced design. Give it try!