There is an eternal dilemma about properly structuring your project/folder composition. React is entirely unopinionated in how you structure and write your code. This flexibility, though, makes it challenging to structure a React project since React sets no standards. Developers have free will and can compose structures independently, but with freedom comes responsibility. So many times, I saw basic structures only containing folders like utils, hooks and components. For a simple and small project, it could be enough. Still, for more complex ones, there could be some issues like:
- difficulties in maintenance,
- project composition may not be immediately self-intuitive.
During my career, I’ve encountered two fully-fledged project structures which are generally better to adapt and incorporate into your projects. In this article, I will show you what I consider intuitive and scalable systems for the large-scale production of React applications.
Simple structure on Steroids
First example of a more mature structure contains a standard structure with additional directories like pages, modules
- components – in this directory we have reusable components which are not connected with any domain and don’t have any business logic. For example buttons, parts of form, modals, spinners, containers etc. This directory should be the first place for searching atomic components which could build for us more specific ones.
- lang – here we can collect translations from the whole application
- pages – here we can have page containers which will contain elements from the components directory as well as the presentational components from modules such as the Main page, user profile, and details page.
- utils – in this folder we can have some helper functions which are not connected with the domain i.e., service for connection with API (abstraction based on fetch API or axis or sth else), Value Objects like Date object, Money Object etc. abstractions for caching data like SWR or ReactQuery
- modules – this directory contains domain abstractions. For example, try to imagine an authentication module. Each module is divided into layers:
- application – in this layer, we have main functions which are helpers in this module i.e. password strength policy
- infrastructure – here we have communication with API. This could contain hooks based on ReactQuery. Here we can also do some mapping from DTOs to models used on front i.e. mapping the date field which is a string when we received it from the backend and we could map it to i.e. Date object or Value Object
- presentation – in this layer we have reusable presentation domain component i.e. login modal which could be triggered in few pages or logout button
- types –this layer have types, interfaces and DTOs
This approach is somewhat mature and works in most cases. Sometimes there were problems with mixing modules because, for example, some types from one module leaked to another module or some presentation components were also needed in another presentation module. Still, in most cases, it worked fine.
Feature Sliced Design: The beauty of layers
Because of the not-entirely-perfect structure of the previous approach, I searched for something fresh and I found a second, even more mature, solution called Feature Sliced Design. Currently, in SolveQ we are using FSD in the complex data tracking application for a US-based Insurtech company.
As the authors said in the documentation FSD is an architectural methodology for scaffolding front-end applications. The main purpose of this methodology is to make the project more understandable and structured in the face of ever-changing business requirements.
The main concept in FSD is layers
The layers are standardized across all projects and vertically arranged. Modules on one layer can only interact with modules from the layers strictly below. There are currently seven of them.
app — app-wide settings, styles and providers.
processes — complex inter-page scenarios. (e.g., authentication)
pages — compositional layer to construct full pages from entities, features and widgets.
widgets — compositional layer to combine entities and features into meaningful blocks (e.g. IssuesList, UserProfile)
features — user interactions, actions that bring business value to the user. (e.g. SendComment, AddToCart, UsersSearch)
entities — business entities. (e.g., User, Product, Order)
shared — reusable functionality, detached from the specifics of the project/business. (e.g. UIKit, libs, API)
Then there are slices, which divide the code by business domain. Keeping logically related modules close together makes your codebase easy to navigate. Slices cannot use other slices on the same layer, which helps with high cohesion and low coupling.
Each slice, in turn, consists of segments. These tiny modules are meant to help separate code within a slice by its technical purpose. The most common segments are
ui , model , api and
lib , but you can omit some or add more as you see fit.
In the project we have introduced a few improvements:
- Pages are structured the same way as in NextJS apps wherein the folder structure defines routing.
- Our features are mainly not simple components. In our more complex features, we sometimes use subfolders like components with simple dumb components and utils with some helper functions.
- We don’t use processes in the first layer because all cases are handled in widgets that connect our features into fully meaningful blocks.
- We currently don’t see a use for the UI subfolder in the segment layer. The features folder handles all UI components. We are using only models and API. In the model folder, we store interfaces, types and DTOs. In api we have hooks based on ReactQuery.
- In the shared folder we have subfolders components, utils, and hooks.
- Components contain reusable atomic components which are not connected with the domain i.e. form elements, input, select, button, containers, spinners etc.
- Utils contain httpClient service with Axios for data fetching, theme, logic with translation, Value Objects and some minor functionalities which could be used in the entire project
- hooks contain reusable hooks from chakraUI, ReactQuery and ReactTable
FSD in my opinion has overcome the most common issues of the simple structure of React app:
- it’s scalable
- it’s self-intuitive and logically coherent
- the maintenance is simple and easy to do
- it is a well-documented standard which is being constantly developed, polished and updated
No matter the size and complexity of your React application a good structure is always crucial. So, is a simple structure always better? Not really. As mentioned in Introduction, the main issues with a simple structure are non-scalability and folders will quickly become a mess. However, the FSD community found a fresh approach that could break this chaos and provide peace of mind in the project structure.
We introduced FSD a few months ago in our large-scale project and I don’t have any doubts about where to place some new code. So, after many years we finally have a good standard for structuring frontend projects conveniently and productively.
If you are looking for a company with experience in software development and a system that works, I advise you to contact us at SolveQ here and have everything under control!