DEV Community

Cover image for Architecting React Apps Like it's 2030
Renato Pozzi
Renato Pozzi

Posted on • Originally published at renatopozzi.me

Architecting React Apps Like it's 2030

There is one problem that every React Developer falls into during his journey. This is how to structure a good app architecture.

This article will help you avoid some common errors that most of us make architecting react applications, and will give you the right way to structure your directories.

Is This For You?

Before starting, it's necessary to underline a point: there is no perfect solution that fits any possible case. This is particularly important to understand because a lot of developers are always looking for the one and only one solution to all their problems, I'm sorry to say that if you are looking for this, this could not be the article for you.

Time to Architect!

If you have arrived here, it means that you are interested in the topic, so, it's finally time to start! All the content I will mention will be put into an src directory, and every mention of new folders will be relative to this constraint, keep it in mind.

Components

What are the first things a React Developer creates in a project? I would say components because you know, React apps are made with components so, no components no party.

During my career, I saw a lot of different architectures (some very good, and others awful..) and I figured out one path which can be used in most cases, even for little projects.

This is what it looks like:

├── components
│   ├── common
│   │   └── button
│   │       ├── button.tsx
│   │       ├── button.stories.tsx
│   │       ├── button.spec.tsx
│   │       └── index.ts
│   └── signup-form
│       ├── signup-form.tsx
│       ├── signup-form.spec.tsx
│       └── index.ts
Enter fullscreen mode Exit fullscreen mode

The key point here is the following: we have components that contain all the components that are used more than one single time in the app, so we are going to exclude every feature-specific component from this folder.

Why? Simply because the meaning of this folder is to contain reusable logic. And I put also a difference between global and scoped reusable logic. A button is supposed to be used on almost every page of our app, that's why a common directory exists. Something different happens instead for the signup-form component, why is this reusable?

Well, let's suppose to have two different pages (more on this later) for sign-in and sign-up, this component needs to be repeated two times, that's the reason why is put into the components folder but as a scoped logic.

Notice how as I said before, this is a specific case, if we had a single page for authentication, we shouldn't have put it in here.

Some examples of what can be inserted into the common folder:

  • Inputs
  • Cards
  • Alerts

I think you got the point.

You probably noticed also that every single component is placed into a proper directory with a very easy-to-understand naming convention.

button
├── button.tsx
├── button.stories.tsx
├── button.spec.tsx
└── index.ts
Enter fullscreen mode Exit fullscreen mode

That's because your app can eventually contain more than 1000 components, and if all of them have a test or a storybook file, this can easily become messy. Let's explore some key points of this folder:

  • All the component-related files are in this folder.
  • All the exportable modules are put into an index.ts to avoid the awful double name in import.
  • All the files are named in kebab-case.

I know it seems a little bit verbose, especially for newbies or for little projects, but it requires very little effort and as a return to having a gain in code readability, want an example? Try to answer these questions:

  • Where is the button component? -> In the button folder.
  • Where are the stories for this button? -> In the button folder.
  • Oh dear, I need to find the test for this button where I can find it? -> Answer by yourself.

Again I repeat, if you feel these questions are silly and obvious, the day will come when you will work on a code base where best practices are the last thing that was considered and you will remember this article.

We're not done with the components yet, but we'll come back to that later.

Pages

Let me tell you a secret, in React, pages do not exist. They are components too, composed with, well, other components. But differently from the other components, usually are very strictly scoped (in a specific URL path for example). Where do we go to insert them?

We can use a practical views (or pages if you prefer) directory, in which put all those stuff, have a look a the example:

views
├── home.tsx
├── guestbook.tsx
└── newsletter
    ├── index.ts
    ├── newsletter.tsx
    └── components
        └── newsletter-form
            ├── newsletter-form.tsx
            ├── newsletter-form.spec.tsx
            └── index.ts
Enter fullscreen mode Exit fullscreen mode

For the home and guestbook it's fairly simple, a page is supposed to be the result of the composition of other components, which have proper tests, so I'm not gonna create a specific directory for them.

The case is different for the newsletter page, which has something specific, a newsletter-form component. In this case, I use the approach of creating a nested component folder inside the page folder and act like I'm in the normal components folder, so using the same rules.

This approach is powerful because lets you split the code into small chunks, but keeps the architecture well organized. The newsletter-form component should not be put into the "main" components folder, simply because here is the only place in which is used. If the application grows, and the component will be used in several parts, nothing prevents you from moving it.

Another tip i usually suggest is to keep a consistent name between the page and the route, something like this:

<Route path="/bookings">
  <Route index element={<Bookings />} />
  <Route path="create" element={<CreateBooking />} />
  <Route path=":id" element={<ViewBooking />} />
  <Route path=":id/edit" element={<EditBooking />} />
  <Route path=":id/delete" element={<DeleteBooking />} />
</Route>
Enter fullscreen mode Exit fullscreen mode

Layouts

Layouts are no pages at all, they are more like components, so they can be treated like that, but lately, I prefer to put them into a layout folder, it makes more clear that in this app there are n layouts available.

layout
├── main.tsx
└── auth.tsx
Enter fullscreen mode Exit fullscreen mode

One thing you may notice is that I don't call them main-layout.tsx but just main, that's because following this reason I would have to rename all the components like table-component.tsx which is weird. So I name all the components without the obvious suffix given by the parent directory, and if I need to underline that I'm using a layout I can always use an import alias as well.

import { Main as MainLayout } from "@/layouts/main.tsx";
Enter fullscreen mode Exit fullscreen mode

Contexts, Hooks & Stores

This is pretty simple, and usually, I see almost every developer stick with something like this, so I'm gonna put here how I organize those things:

hooks
├── use-users.ts
└── use-click-outside.ts
Enter fullscreen mode Exit fullscreen mode
contexts
├── workbench.tsx
└── authentication.tsx
Enter fullscreen mode Exit fullscreen mode

Here again, I stick with using kebab-case for all the filenames, so I don't have to worry about which ones are capitalized and which are not. For the testing files, due to the fact, that the custom hooks are few, I would not create a specific folder, but in my opinion, if you want to be very strict. you can do it as well:

hooks
├── use-users
│   ├── use-users.ts
│   ├── use-users.spec.ts
│   └── index.ts
└── use-click-outside.ts
Enter fullscreen mode Exit fullscreen mode

Helpers

How many times do you create a nice formatCurrency function without knowing where to put it? The helpers folder is coming to your help!

Usually, here I put all the files I use to make code look better, I don't care if the function is used more than one time or not. Usually, these helpers are fairly few, so until there is a very large number of them, I stick this way.

helpers
├── format-currency.ts
├── uc-first.ts
└── pluck.ts
Enter fullscreen mode Exit fullscreen mode

Constants

I see a lot of projects that contain contansts in the utils or helpers folder, I prefer to put them into a specific file, giving the user a nice view of what is used as a constant in the app. Most of the time I put only globally scoped constants, so don't put the QUERY_LIMIT constant here if it is used in only one function for a very specific case.

constants
└── index.ts
Enter fullscreen mode Exit fullscreen mode

Also, I usually keep all the constants in a single file. It is no sense to split every constant into a specific file.

// @/constants/index.ts
export const LINKEDIN_FULLNAME = "Renato Pozzi";
export const TWITTER_USERNAME = "@itsrennyman";

// And use them in your app! 👍
import { LINKEDIN_FULLNAME, TWITTER_USERNAME } from "@/constants";
Enter fullscreen mode Exit fullscreen mode

Styles

Simply put global styles into a styles folder, and your game is done.

styles
├── index.css
├── colors.css
└── typography.css
Enter fullscreen mode Exit fullscreen mode

What about CSS for my components?

Good question mate! Do you remember the component folder we talked about a little while ago? Well, you can add more files depending on your needings!

button
├── button.tsx
├── button.stories.tsx
├── button.styled.tsx
├── button.module.scss
├── button.spec.tsx
└── index.ts
Enter fullscreen mode Exit fullscreen mode

If you are using emotion, styled-components, or simply the CSS Modules, put them into the specific component folder, so everything will be optimally packaged.

Config Files

Does your application have configuration files, like Dockerfiles, Fargate Task Definitions, and so on? The config folder should be the perfect place for them. Putting them into a proper directory avoids root directory pollution with non-relevant files.

APIs

99% of the react application have at least one API call to an external endpoint (your backend, or some public service), usually these operations are performed in a few lines of code without too much difficulty, and that is why in my opinion an optimal organization is underestimated.

Consider this piece of code:

axios
  .get("https://api.service.com/bookings")
  .then((res) => setBookings(res.data))
  .catch((err) => setError(err.message));
Enter fullscreen mode Exit fullscreen mode

Quite simple right? Now imagine you have these 3 lines spreaded across 10 components because you use a lot of time this particular endpoint.

I hope you don't want to do a search and replace for all the URLs in the app, furthermore, if you are using TypeScript, import every time the response type it's quite repetitive.

Consider instead using an api directory, which first of all contains a consistent instance of the client used for the calls, like fetch, or axios, and also the files keeping the fetch calls declarations inside!

api
├── client.ts
├── users.ts
└── bookings.ts
Enter fullscreen mode Exit fullscreen mode

And an example of the users.ts file:

export type User = {
  id: string;
  firstName: string;
  lastName: string;
  email: string;
};

export const fetchUsers = () => {
  return client.get<User[]>("/users", {
    baseURL: "https://api.service.com/v3/",
  });
};
Enter fullscreen mode Exit fullscreen mode

Wrapping Up

It's been a long road, and I hope the information in this article is useful to you as you build your new and existing projects. There is still a lot to say, there are always special cases to take into consideration, but the points covered in this article are the most used by all react developers.

Do you also use one or more of these techniques in your projects? Let me know via Twitter or LinkedIn!

Discussion (35)

Collapse
mabouhha profile image
боженька-нерд

this is a good article, but not enough for large spa (more than 1000 react components) with rich business logic. Previously, I used this approach, and everything turned into a dump. I found the solution in the Feature Sliced Design architecture. I advise those who work on large SPAS to familiarize themselves. feature-sliced.design/en/docs/intro
For business card sites, FSD is overhead.

Collapse
itsrennyman profile image
Renato Pozzi Author

Thanks for the link you shared! I'm gonna have a look also at it!

Collapse
ekeijl profile image
Edwin

This looks really good. I tried it a bit today, but I get confused where to place custom hooks and context, I'm not sure if hooks translate really well to this architecture.

From the example repos I deduce that data fetching hooks go in the model segment, UI related hooks in the UI segment.

When using React.Context, I have a Provider at the top level, so that needs to go in the app layer, but the Context can be accessed by lower layers to read the Context so the React.createContext goes in the shared layer?

Collapse
mabouhha profile image
боженька-нерд

Yes, all right.
The model segment is your little piece of domain, an aggregate (in DDD terms). Data fetching 100% should be located here.

UI related hooks - yes, they can be put in the ui segment. But first I ask myself the question: "Will there ever be a component in the application in which I can reuse this hook? Is such a component located in another segment? How sure am I?"
If the hook will be used only in one ui segment, it remains in this place.
If I'm not sure, then I move it to the lib segment.
If the hook is 100% reused, then I transfer it to shared/lib.

React.context - also depends on the usage. If you use it in several slices, then /shared. If in one slice, then put it right here. But if there is a need to use multiple slices, it's worth thinking about: "Is it certain that this data cannot be encapsulated in a single entity? Or do I really need a complete set of this data in all places? Will I benefit if I encapsulate context data in multiple entities? Is there anything to gain from encapsulating the context?"

Collapse
joelbonetr profile image
JoelBonetR • Edited on

This post has my Quality Check! 😁

I'd just add a styles file along with a component, i.e.

├── components
   ├── common
      └── button
          ├── button.tsx
          ├── button.stories.tsx
          ├── button.spec.tsx
          ├── button.styles.js
          └── index.ts
Enter fullscreen mode Exit fullscreen mode

While using styled-components. You can use it along TS without issues of course, just import the cssprop in your project -> see the doc for further details.

This way we cover the issues related to a growing project in which classNames are generated indiscriminatedly. Also it provides inheritance (while other solutions like Tailwind does not) and provides a straight way to pass props to the styles for modifications.
i.e.

 const Button = styled.button`
    padding: 8px 12px;
    border: none;
    border-radius: 4px;
    background-color: ${(props) => (props.status === 'error' ? 'red' : 'green')};
  `;

<Button status='error'>{message}</Button>
<Button status='ok'>{message}</Button>
Enter fullscreen mode Exit fullscreen mode

This way we let the design logic inside the styles where it belongs :)

Best regards!

Collapse
itsrennyman profile image
Renato Pozzi Author

Thanks for the tip! Yeah this is definitely a good addition to this architecture for styled-components/emotion users.

Collapse
yogeshktm profile image
yogeshwaran

Great one.

Very useful for react starters like me. also i saw some of the projects used folder/concept like Container which have stateful logics of components inside it.

Is that container concept still a best practice ? and how you maintain states for components. since i am also used this container for few projects so got confused whether its still valid one

Thanks

Collapse
itsrennyman profile image
Renato Pozzi Author

The point is: that there is no best practice at all, but there are good and bad solutions to specific problems. I use container patterns a lot. Because it keeps my function components easy to maintain avoiding side effects inside of them.

Collapse
yogeshktm profile image
yogeshwaran

Thanks for the reply :)

Thread Thread
itsrennyman profile image
Renato Pozzi Author

You're welcome!

Collapse
localpathcomp profile image
Garrick Crouch

Good job. You may want to use a services directory for anything that interacts with the outside world such as http/wss clients. Hooks are a great way to encapsulate using those clients ie swr/react query

Collapse
itsrennyman profile image
Renato Pozzi Author

Yeah, that's another good point. Thanks for sharing this!

Collapse
roccoluke profile image
Luke Rocco

Good advice 👏 Been using this architecture for a while though. In terms of directories, when dealing with large enterprise apps, components folder is usually subdivided more grnaaularly, such as using atomic design method. Same goes for hooks/store/context, get subdivided by feature.

Collapse
itsrennyman profile image
Renato Pozzi Author

Good point, yeah i think is definitely affordable for bigger applications.

Collapse
aishanipach profile image
Aishanii

Love how simple yet effective this is. React is perfect for people who love organised code. For the repetitive API call, I think redux store can be used and a redux folder with procedure based sub folders, namely actions, store and container, would work better.

Would love to see more project specific techniques! :D

Collapse
itsrennyman profile image
Renato Pozzi Author

Thanks for the comment! :)

Collapse
jackmellis profile image
Jack

As someone who spends 90% of their time in business logic, just throwing api in as an afterthought is a sad but very common move (although still significantly better than just stuffing api calls into your components).

I always ensure any medium-large scale application has something like onion/hexagonal architecture, where business logic has a defined home, api calls are abstracted into tech-agnostic functions, and there is a distinct divide between presentation and operations.

My ui layer does look a lot like this, but it really should just be one part of the application heirarchy, not the entire focus of it.

Collapse
raibtoffoletto profile image
Raí B. Toffoletto

Great Article! 🎉 I agree with most of it (a part of kebab-case.... 😶) and usually follow this structure. Folks can organize stuff differently, but the most important is to be consistent!! So anyone new can find themselves after a bit of investigation. 😁

Collapse
itsrennyman profile image
Renato Pozzi Author

I totally understand you, i used to hate kebab-case.. but after a while of forcing me keeping it, i'm starting to love it.

Collapse
madza profile image
Madza

This was a good read 👍✨💯

Collapse
itsrennyman profile image
Renato Pozzi Author

Thank you so much!

Collapse
madza profile image
Madza

My pleasure 👍✨💯

Collapse
wkrueger profile image
wkrueger

in 2030 nobody will be using this mess of a framework

Collapse
andrewbaisden profile image
Andrew Baisden

Interesting way to look at it. There are countless ways to solve this problem and these were really good.

Collapse
blitzstudio profile image
Ionut Alexandru Tudor-Pricop

Nice article. Thx, I am new to React but i could go through witout any issues. It definitely gave me a good direction. 😀Coding

Collapse
itsrennyman profile image
Renato Pozzi Author

You're welcome!

Collapse
kwirke profile image
Kwirke

Good article about folder organization. However, "architecture" is a much broader subject that involves data and state management, abstractions and interfaces used, extensibility, bug traceability, documentation, client-server communication, and client bundling and serving, among others. So there's a bit of a clickbait there.

Collapse
dalsoft profile image
DalSoft • Edited on

Enjoyed the post I had similar thoughts a while back and come up with something similar:
app1/
├─ src/
│ ├─ Components/
│ ├─ Apis/
│ ├─ Assets/
│ ├─ Components/
│ ├─ Context/
│ ├─ Hooks/
│ ├─ Pages/
│ ├─ Styles/
│ ├─ Utils/

I turned it into a GitHub template main thing for me is exactly what you said everything related to the component styles, tests etc should live in the same folder. I've kept to this with very large apps, and it works really well as your app grows.

Collapse
pbergwerff profile image
pbergwerff

For sake of scalability I would split up your components by using Atom Design Pattern.

atomicdesign.bradfrost.com/chapter-2/

Collapse
joelbonetr profile image
JoelBonetR

In my experience that never ends well IRL 😆 It has the same issues than CSS classNames. When people can't find the 'desired' atom they create a new one with a different name (language is sometimes a barrier isn't it?)

Collapse
itsrennyman profile image
Renato Pozzi Author

I personally experienced the same.

Collapse
bryangranado profile image
Bryan Granado

How did a services folders ? or only is an example for files ?

Collapse
callmehomer profile image
Kamil Homernik

Did I get it right? When you have index.ts in you button folder you don't need to import it by writing '... from 'components/button/button'' and just 'components/button' will work?

Collapse
talweltsch profile image
Tal Weltsch

Hey! good read!
Messaged you on twitter!

Collapse
cdcwatson profile image
Craig

Any interesting edge cases to consider?