Best Scalable React App Architecture 2020
Today I will show you how I built a Best Scalable Architecture for ReactJS Applications. First, have a look at my directory structure then I will explain every bit I did or used in creating this architecture. I created this architecture with the help and guidance of the Technical Lead (Eugene Pyatibratov) in my previous organization and then I enhanced it as and when required.
I have used this architecture in 5+ projects of mine and have been continuously enhancing this to date and will continue to do so in the future. I have created a Pre-configured React Starter Kit which is like plug and play for your new projects, everything is configured so you just have to take care of the development. Check on Github and don’t forget to give STAR the repo.
Now let’s start discussing things I did in this architecture and why I created such folder structures. You might have seen many different styles of architecture and might have created your own as well, I hope you will like this as it will give you more clarity on files and structures and easy to scale the application.
Project Structure for Scalable React App Architecture
Big and extensive ReactJS application should have a well planned and organized project structure. The best way is to use a mix of strategies to achieve better results as I am going to describe next.
Top-level project architecture (which is under src/ folder) should be organized by type. No files should be here, just folders. This way it will be clear and understandable. Similar to a home where you have a foundation, walls, roof and etc. Under these walls are rooms, but maybe you don’t want to go there if you have some work outside like for instance painting walls. Having files in here adding mess. We should keep it clear like this:
- src/ - main/ - modules/ - library/ - resources/
One really important note is that every folder under src/ should be accessible from an absolute path. We don’t want to have relative paths simply because they add a big mess in a code and it’s harder to figure out where some logic is coming from. The only exception is the modules folder and we will talk about it later in this article.
src/main. For a big and extensive application, we should have a place somewhere to put configuration files related to our application logic so that we could extend them later and to make sure that we don’t add some extra mess in our code. This is how it looks like:
- src/main/ - axios/ - index.js - routes - index.js - PrivateRoute.js - store/ - index.js - mainReducer.ts
src/library. In big applications, we usually have common components, some functions or utilities. We should keep them in one place to make sure that we don’t spend time making up something that is already in our project. The simplest example could be a custom fetch function around Axios so that we can import it from our library and use anywhere, or a Button component. Let’s have a look at the library/ folder structure:
- src/library/ - common - components - Header - index.jsx - styles.scss - Dropdown - index.jsx - styles.scss - actions - AuthActions.js - constants - StoreConstant.js - ImagesConstants.js - URLConstants.js - reducers - AuthReducer.js - utilities - Validators.js - Storage.js - api - (optional folder as per requirement create this) - AuthApiService.js
Have a look at this common/ folder. Why do we need this in our application? Talking about common components, the answer is pretty straight forward – common components are used in different parts of an application, but what about actions, reducers, and etc.? The point is that we can have some data coming from the server that can be used in different parts of an application.
More than one component wrapped in a container could use that data, but at the same time, those components can be placed on different screens. It means that we can’t place this data in a certain module, related to a certain screen. Well, we could, but we will have a twisted logic and it will be harder for everyone to understand where to take some logic from and where to write functions around this data. This way we could face some issues related to doubled actions or selectors and etc. To avoid this, we should use a common place to put this logic.
This is really important to say, that everything under common/ folder is organized by type. It means that if we have a user reducer, we should also have user actions and etc.
And there is one more convention which is – all data loaded from the server should be placed in a common reducer folder and should be called as “entities”. This way we will keep our store clean and will have a certain place for all our loaded data. Other modules will be able to use this data from entities reducer. Actions to request some entities should be places in the common actions folder as entitiesActions.ts
To understand how to use entities reducers in modules, we need to have a look at what are modules first, and this is what we are going to do next in the src/screens section.
src/modules. Screens are modules. In a web, each certain module represents a separate page. On mobile we call it “screens”, but there is no difference in meaning. Each screen is a self-organized structure of components, logic and etc. The module can have an access to the store. Let’s have a look at the structure of a module first:
- src/modules/ - Dashboard/ - index.jsx - dashboardStyles.scss - dashboardActions.js - dashboardConstants.js - dashboardReducer.js - frames/ - HeaderFrame/ - index.jsx - headerFrameStyles.scss - CoolFrame/ - inex.jsx - coolFrameStyles.scss
There is a difference between what we’ve got here in modules and in the common library directory. Here in modules/, we have a “module structure” for each screen. In a library common/ folder we have files structured by “type”. This why it is called “mixed” architecture.
An important note is that each module can reach to anything that we have in a library folder, but not vice versa. For instance, is we have a Dashboard screen, on that screen we want to get some data from our entities, loaded from the server. We can import that reducer from entitiesReducer and use it in our Dashboard/index.js.
src/resources. All resources that we are going to have we should keep in a separate folder like this. We could keep these files in certain modules, but this is not extensive and can bring some mess in our code. Imagine that we have an icon somewhere in our code in one of our modules. If we don’t have a centralized place for all of our icons, we can face a situation where similar icons will be added to the project by different developers, and the project bundle will be heavier. This is critical for the application. And this is not only about icons, we can have some other resources. There is a list of them here:
- src/resources/ - images/ - logo.svg - styles/ - variables.scss - mixins.scss - fonts/ - Roboto.ttf - seed/ - country.json
Having this type of architecture we can easily extend any part of an application. Even adding new technologies in a stack is getting much simpler, since everything is placed in the right place.
And lastly, imports should be absolute and organized in a way that it is easy to understand what type of imports are there. The order should be like this.
import React from 'react'; import { uniqBy } from 'lodash'; import AppNavbar from 'library/common/components/AppNavbar'; import './style.scss';
I hope you like this Scalable React App Architecture and you can use my starter pack which complies with this architecture and will leverage your efforts in the initial project setup. It has everything configured such as Axios Instance, Redux, ESLint/Prettier, Pre-Commit Hooks, React Router with Private Routes and Absolute Imports, etc. Clone from here and don’t forget to give Star to the repo also check my other article on react js development.
Suggestion, What if we remove axios from main and put in a new folder called infra under src/ ? So that way, we know everything in the infra folder can interact with external systems by receiving, storing and providing data when requested.
Yes we can do it. I have only one question here is, what all files we will keep in infra?