How to Develop a Modern React Project at Scale

8
minutes
Mis à jour le
19/11/2020

Share this post

Start developing your React app using this modern React code structure used at Sipios to build scalable and maintainable React apps!

#
Front-end
#
Javascript
#
Modern React
#
PWA
#
Performance
#
React
#
Typescript

You have in mind to start a new React app but you have no clue what tools to use and how to use them. Fortunately for you, there's a list of all the best modern React libraries to use (and why) in this article: go check them out!

Once you have choosen all the best tools to build your React project, you need to know how to use them. Knowing how to use them implies knowing how to organize your code so that you naturally enforce the best development practices related to the tools you use.

📚 The theory

I like to see the optimal code architecture as the result of the minimization of 3 quantities:

  • Search cost: how much time a developer will spend searching for a specific feature
  • Onboarding cost: how much time a new developer will spend taking charge of your project’s development practices
  • Development cost: how much time a developer will spend creating a new feature

Carefully keeping track of these quantities, there are many reasons why you’d care about how you organize your code:

  • You want your code architecture to tell you what development practices you enforce and how to enforce them
    Objective: minimizing onboarding cost
    Example: future developers onboarded on your project should intuitively integrate your development practices
  • You want your code architecture to let you enforce development practices as easily as possible
    Objective: minimizing development cost
    Example: creating a component should be one of the easiest things to do
  • You want your code architecture to prevent you from enforcing different development practices
    Objective: minimizing development cost
    Example: if you have chosen to use styled-components, styling a component using Material-UI should feel unnatural
  • You want to know where to find specific code easily (ie your code must be split into several files, each indicating its unique purpose)
    Objective: minimizing search cost
    Example: you want to change the design of a button: search for a specific styling file

📷 The big picture

Alright, let’s not beat around the bush a little more; Below is a file arborescence which will help you enforce the best practices for React and speed up your development by helping you find what you need, where you intend it to be — given the setup you just installed.

👇 You will find more detailed information in the following subsections. Don’t hesitate to check this out again when reading.
You’ll find a repository to generate this kind of architecture and each of its elements at the end of this article!
src/
... components/
... .......... MyAwesomeComponent/
... .......... .................. components/
... .......... .................. MyAwesomeComponent.component.tsx
... .......... .................. MyAwesomeComponent.container.tsx
... .......... .................. MyAwesomeComponent.hooks.ts
... .......... .................. MyAwesomeComponent.style.ts
... .......... .................. index.ts
... modules/
... ....... MyAwesomeModule/
... ....... ............... MyAwesomeModule.actions.ts
... ....... ............... MyAwesomeModule.selectors.ts
... ....... ............... MyAwesomeModule.reducer.ts
... ....... ............... MyAwesomeModule.saga.ts
... ....... ............... MyAwesomeModule.types.d.ts
... ....... modules.d.ts
... ....... reducers.ts
... ....... sagas.ts
... ....... store.ts
... pages/
... ..... MyAwesomePage/
... ..... ............. components/
... ..... ............. pages/
... ..... ............. MyAwesomePage.component.tsx
... ..... ............. MyAwesomePage.container.tsx
... ..... ............. MyAwesomePage.hooks.ts
... ..... ............. MyAwesomePage.style.ts
... ..... ............. index.ts
... services/
... ........ types/
... ........ ..... MyAwesomeService.types.d.ts
... ........ MyAwesomeService.ts
... ........ style.ts
... ........ theme.ts
App.tsx
App.router.tsx
index.tsx

♻️ Components — where Global Components are stored

Why?

One of the most important React development paradigm is component reusability: you don’t want to rewrite a component’s logic twice — with some modifications indeed — if its purpose can be generalized so that its behavior covers both the use cases.

Once you have created a generalized component, you don’t want to import it from where it was first used/created: it just doesn’t make sense. Imagine someone else trying to find it: that someone else needs to have one specific place where to look for it. This place is indeed the components/ directory; which makes the components stored inside Global Components.

How?
  • It has a recursive definition: any component can be made of smaller components, stored under components/
  • MyAwesomeComponent.component.tsx contains the component’s rendering logic: what hooks are invoked, how things are displayed and never more (keep things clear and concise)
  • MyAwesomeComponent.container.tsx (facultative) contains the component’s Higher-Order definition: basically where you connect your component to your store:
export default compose(
connect(mapStateToProps, mapDispatchToProps),
React.memo,
)(MyAwesomeComponent);
  • MyAwesomeComponent.hooks.ts (facultative) contains the component’s own hooks: instead of creating your form and related submit/handler functions in the functional component’s body, create a specific hook, import it and call it
  • MyAwesomeComponent.style.ts (facultative): contains the component’s level styled-components: instead of creating your styled components inline just aside your functional component’s body, create it in this file and import it
  • index.ts: Used for exporting the component at the directory level

📬 Modules — where the state’s magic happens

Why?

Since you are managing a normalized state in your app, you’ll need somewhere to check how we are handling actions, sagas, how related selectors, types are defined; and you will need this place to be organized as your state is, for simplicity.

The important thing here is that you have distinct action files for actions related to distinct parts of your normalized state, once again to minimize search cost and onboarding cost. This means you need to have distinct Author.actions.ts and Book.actions.ts. Indeed, this rule follows for selectors, reducers, sagas and types.

How?

Every module — defined as a part of the state, referred below as the relative state — shall register a subdirectory of modules/ containing :

  • MyAwesomeModule.actions.ts : the set of actions responsible for the management of the relative part of the state
  • MyAwesomeModule.selectors.ts : the set of selectors allowing you to select each individual part of the relative state (to prevent useless re-rendering), and some additional selectors to aggregate them as needed according to the UI
  • MyAwesomeModule.reducer.ts : the Redux reducer, responsible for the behavior of the relative state
  • MyAwesomeModule.saga.ts : the Redux saga, listening for this module’s actions
  • MyAwesomeModule.types.d.ts : the types declaration file associated with the relative state, to use them wherever needed without the need to import them

You’ll note that module/ also contains basic files associated with a React + Redux app, namely:

  • reducers.ts : aggregate all the modules’ reducers into one reducer
  • sagas.ts : aggregate all the modules’ sagas into one saga
  • store.ts : create, enhance and exports the redux store
  • modules.d.ts : redefine the DefaultRootState type for convenient purposes

📃 Pages — where your most top-level page components are

Why?

Your React app will definitely allow the user to navigate between several pages — which are indeed React components — that you don’t want to store inside your components/ folder because you want intuition to guide future developers of your project: where’s the first place you would look at to check a behavior happening on your homepage?

⚠️ Spoiler alert: Under pages/homepage/

The trick here is to carefully define what a Page is: let’s define it as a unique view, accessible only via a change in the URL.

How?

The main thing to note here is that we have the same arborescence as for the Global Components, plus the recursive definition of the pages/ subdirectory — once again to ensure that when searching for a piece of code, we look at the right place, the first time. Also, you don’t want to use components tied to a specific parent component’s context in other places of your app: that’s why you don’t always declare Global Components and instead declare them in the components/ subdirectory of the parent component/page.

One additional thing to know is that if you define one or more pages under a page, you will need a router to display them correctly: this router should be created as a component of the parent page, and invoked inside the parent page's component:

... pages/
... ..... MyAwesomePage/
... ..... ............. components/
... ..... ............. .......... PageRouter.component.tsx
... ..... ............. .......... -> defines sub-routes to sub-pages
... ..... ............. pages/
... ..... ............. ..... MyAwesomeSubPage/
... ..... ............. ..... -> defines one of the sub pages
... ..... ............. MyAwesomePage.component.tsx
... ..... ............. -> invokes <PageRouter />

AppRouter.tsx
-> defines a route to MyAwesomePage

💻 Services — where the computing happens

Why?

Basically, this is where all the stuff only used at render time goes: utility functions, rendering constants, business logic, hard computing. Most of the time, specific types come up when writing utility functions or computing: you’ll definitely need a declaration file for these — a specific one, once again to know where to find the related types.

How?

This folder is less governed by rules than the others, because of its more vague definition. However, I recommend that each service — defined as a set of functions used only at render time — register a file of services/.

Additionally, services/ shall contain a types/ subdirectory, containing each type declaration file for each service.

Finally, when using Material UI and styled-components, you’ll definitely need each of these files:

  • style.ts : design constants and functions used throughout the app
  • theme.ts : custom Material UI theme export

⚛ App files — all the stuff related to the most top-level configuration

Why?

Have you ever found yourself onboarded on a javascript project?
The first thing you look after in a javascript project is the root index.js, to lead you into figuring out how the app is working globally — because it’s the very first file javascript will read. On a React TypeScript project, you are basically looking for this file as index.tsx: the most top-level app setup file.

❗️ This rule also applies to any top-level file you could have in your project.

How?

Most of the time, this file sets up the portal for your app by referencing the most top-level component of your App, defined in another file, plus some additional top-level configuration.

As for every React app, you’ll need a router file to at least define the most top-level routes of your app: let’s keep it at the root directory for simplicity too.

How can I build my next React project fast using this scalable structure?

I created a template repository allowing you to start a React project with all the best tools I presented and some generators to help you develop faster, without losing scalability and robustness.
You are free to star this template repository ✨

This article is the second part of a larger trilogy, which goal is to give you all the best tools, advise you on how to use them and show you all the pitfalls you need to avoid when developing with React.
The first part can be read here. The third and last part will be released next week 🚀
Subscribe to not miss it!