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
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
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
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>
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
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";
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
contexts
├── workbench.tsx
└── authentication.tsx
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
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
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
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";
Styles
Simply put global styles into a styles
folder, and your game is done.
styles
├── index.css
├── colors.css
└── typography.css
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
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));
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
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/",
});
};
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!
Top comments (37)
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.
Thanks for the link you shared! I'm gonna have a look also at it!
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?
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?"
This post has my Quality Check! 😁
I'd just add a styles file along with a component, i.e.
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.
This way we let the design logic inside the styles where it belongs :)
Best regards!
Thanks for the tip! Yeah this is definitely a good addition to this architecture for styled-components/emotion users.
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
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.
Thanks for the reply :)
You're welcome!
in 2030 nobody will be using this mess of a framework
I think React LIBRARY is good enough when you know how to explode and get the best from the bundle. Sometimes the mess is in our side.
For an ordered and visible component hierarchy when talking about Views, Interfaces, etc … I recommend you to go for NextJS Framework.
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
Yeah, that's another good point. Thanks for sharing this!
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
Thanks for the comment! :)
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.
Good point, yeah i think is definitely affordable for bigger applications.
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.
This was a good read 👍✨💯
Thank you so much!
My pleasure 👍✨💯
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. 😁
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.