DEV Community

Cover image for Scalable and Maintainable React Project Structure Every Developer Should Use.
Thomas Sentre
Thomas Sentre

Posted on • Edited on

Scalable and Maintainable React Project Structure Every Developer Should Use.

A good project structure can have a huge impact on how successful a project is in terms of understanding the codebase, flexibility, and maintenance. Projects that are not well structured and maintained can quickly become a big mess and dreadful legacy that no one is too happy to work with.

I will now show you the structure that I very often use in my projects, and explain the reasoning behind it. This structure should be a good starting point for a large-scale application, and you can expand on it depending on the project’s needs. Here is the src structure I can recommend for most projects:

Image React

Let’s cover the folders from top to bottom.

tests

First, we have the tests folder, which will contain all the test file of the React application. I use Jest in most of my CRA application. I see that CRA opted for the use of filename.test.js format when writing tests. But this approach is a bit cumbersome on the eyes and brain. You can write tests in a folder named tests and jest will automatically run the tests in that folder.

api

The api folder contains the API Layer of our application. It will have methods that are responsible for performing API requests and communicating with a server.

assets

The assets folder contains fonts, images, and videos. In the fonts, you can keep any custom fonts and typefaces. In images store any pictures used throughout the application.

components

The components directory contains two directories: common and specific. The common directory will contain any reusable components that are commonly used throughout the application. For instance, buttons, form components, components related to typography, and so on. Any components that are not as common would be placed inside of specific components.

Hooks

The hooks directory, as the name suggests, would hold any custom and reusable hooks. Note that any hooks that are not really reusable, but are coupled to a specific feature, should be placed in the same directory as that feature. For instance, imagine we have a newsletter form component that contains a form to sign up a user for a newsletter. This component could utilize a hook called useNewsletterSignup that would handle signing up a user. A hook like this shouldn’t be placed in the global src/hooks directory, but rather locally, as it is coupled to the NewsletterForm component. Here’s what it could look like:

Image hooks

context

The context directory should contain any global-level context state providers.

layout

Layout directory, as the name suggests, should have components that provide different layouts for your pages. For example, if you are building a dashboard application, you could render different layouts depending on if a user is logged in or not.

config

In the config directory, you can put any runtime config files for your application and third-party services. For instance, if you use a service like Firebase or OIDC for authentication, you will need to add configuration files and use them in your app. Just make sure not to confuse config with environmental variables, as anything that goes here will be present in the build bundle.

constants

Here you can put any constant variables that are used throughout the application. It’s a good practice to capitalize your constants to distinguish them from other variables and localized constants in your app.

Below are some examples of defining and using constants:

  • Define constants separately


// in constants/appConstants.ts
export const APP_NAME = 'Super app'
export const WIDGETS_LABEL = 'Widgets'


Enter fullscreen mode Exit fullscreen mode


// Somewhere in your app 
import { APP_NAME, WIDGETS_LABEL } from '@/constants/appConstants' 
console.log(APP_NAME) 


Enter fullscreen mode Exit fullscreen mode


// You can also grab all named exports from the file 
import * as APP_CONSTANTS from '@/constants/appConstants' 
console.log(APP_CONSTANTS.WIDGETS_LABEL) 


Enter fullscreen mode Exit fullscreen mode
  • Define related constants in one object


// in constants/appConstants.ts 
// Create an object with constant values 
export const apiStatus = { 
IDLE: 'IDLE', 
PENDING: 'PENDING', 
SUCCESS: 'SUCCESS', 
ERROR: 'ERROR' 
} 


Enter fullscreen mode Exit fullscreen mode


// Somewhere in your app
 import { apiStatus } from '@/constants/appConstants' 
console.log(apiStatus.PENDING) 


Enter fullscreen mode Exit fullscreen mode

helpers

Any utilities and small reusable functions should go here — for example, functions to format date, time, etc.

intl

This directory is optional. Add it if an application requires internalization support. Intl, also known as i18n, is about displaying the content of an app in a format appropriate to the user’s locale. This content can include but not be limited to translated text or specific format of dates, time, and currency. For instance, whilst the UK uses DD/MM/YYYY format, the US uses MM/DD/YYYY.

services

In larger applications, we might have complex business logic code that is used in a few different places. A code like this is a good candidate to be extracted from components and placed somewhere else, and the services folder is a good candidate for that.

store

The store folder is responsible for files related to global state management. There are many state management solutions that can be used for React projects, such as Redux, Zustand, Jotai, and many many more.

styles

You can put global styles, variables, theme styles, and overrides in the styles folder.

types

Here you can put any global and shareable types. With this approach, you can save time and more easily share the TypeScript code and types you and your team need.

views

Usually, the views directory only contains route/page components. For example, if we have a page that is supposed to allow users to view products, we would have a component Products.tsx in the views folder, and the corresponding route could be something like this:



<Route path=/projects element={<Products />} />


Enter fullscreen mode Exit fullscreen mode

There is a reason why I said “usually”, though. Many applications have route components in the views, and the rest of the components for it are placed in the components folder. This approach can work for small to medium applications but is much harder to manage and maintain when the number of pages and components grows.

Summary

Choosing the right folder structure is not an easy task. You need to agree with the team on the structure that suits the application, and what might suit one need, might not suit another. Should hopefully be valid for the next years to come. What would you do differently to ensure a good, maintainable structure? Let me know in the comments below.

THANK YOU FOR READING
I hope you found this little article helpful. Please share it with your friends and colleagues. Sharing is caring.

Connect with me on various platforms

Top comments (37)

Collapse
 
nubuck profile image
Barry Buck • Edited

Grouping code by faculty like controllers, components, hooks etc is an anti pattern. Means all your related code is shattered and spread throughout somewhat nondescript folders.
Rather group code by feature, for example all code related to the "accounts" feature goes in an accounts folder with all related routes, components, hooks, fx, actions, reducers etc together.
This reduces cognitive load and makes locating code later on way easier.
Common code used by multiple features can go into a parts folder holding sub features within for example parts/access-control
If you really want to be efficient only ever store one function per file, then your code is searchable from an os level and you don't need to read x amount of lines to find the function you're looking for.
With a feature driven convention you can scale your code base into a mono repo and make all your features packages if needed

Collapse
 
brense profile image
Rense Bakker

This ☝️
Also, definitely do not seperate your unit tests. The unit tests for a component should be in the same directory as the component itself. I really really hate having to navigate through all the code when a test fails. It is much better dev experience if you can just go to the failed test and the component will be right there in the same directory.

Collapse
 
webjose profile image
José Pablo Ramírez Vargas

I am a believer that your folder structure should support the architecture you are pursuing. I think calling this "anti pattern" is too much.

Let me explain with an example I think puts in perspective that niether the article's or your approach works.

If you layer code (which you should), and later on you decide that a layer needs replacement, wouldn't it be super nice to just be able to remove a folder and that's it? If I were to go your way, I would have pieces of the layer I want to remove everywhere. Not good.

This is why I originally commented:

While ordering code files does go a long way, I don't really think there's a single recipe that can be applied everywhere.

Your folder structure needs to facilitate maintenance. That's it. No "patterns" here.

Collapse
 
nubuck profile image
Barry Buck

Define "layer" in a modern stack context. Layers generally refer to data access and business logic from legacy N-tier architectures which lean towards being intrinsically monolithic and DI heavy.
In your scenario replacing a "layer" implies commonly used parts like access-control, transmission, subscriptions, elements or some kind of modifier or service that would be widely consumed.
If so, how is that different from a parts sub-feature or package in a Lerna/Nx mono repo?

Thread Thread
 
webjose profile image
José Pablo Ramírez Vargas

If you have feature "accounts", imagine having dozens of those. I don't see myself ever trying to browse a folder tree like that. No way.

Layers are indeed how you chose to implement your functionality in terms of smaller "pieces". HTTP stack / Validation / Business Rules / Data Storage. That would be typical, I would say. If I chose a particular validation technology/package/whatever and then wanted to replace it, I don't want to chase around in every folder things I need to delete, no sir! I want to delete a folder, then create new implementations of the adapter that connects the deleted layer with the other layers using my new selection. A different approach requires the developer to scan all folders because you never know where the layer could pop up.

Define "DI heavy", because your entire application should be running on DI. Composition Root pattern -> Consumers. Ideally, nothing is left out of DI.

Thread Thread
 
nubuck profile image
Barry Buck

Dependency Injection as you'd expect it in Angular or C#, class instances implementating an interface passed at run time. Object oriented nightmares to reason about.

I don't consider React/Redux a DI heavy technique as the state relationships between parent and children props and context is declared when writing, not inferred at runtime.

The case for bulking business logic validation and transports together for all areas and features of the system is imho monolithic and the trait of object oriented architects that rely of obtuse structures like adapters, factories and all that heavy machinery, which kinda defeats the elegance and beauty of using the declarative React ecosystem imho

Thread Thread
 
webjose profile image
José Pablo Ramírez Vargas

Ok, then define "monolithic" in the context of React or front end, because in my head, the opposite is microservices or micro frontends. Even if you create micro frontends, your layers would still be complete within the confines of that single micro frontend, and I would still stand by my point of view.

But I think I see what the "issue" (cuz not really an issue, just your own feeling and you are entitled to it) is here. You dislike many design patterns when applied to React (at least, maybe all of frontend dev?). I see now.

To each its own. This right here is exactly why I said one structure cannot possibly fit all! You have an aversion towards several design patterns. That's ok. I don't. Therefore your folder structures and not for me. Same goes the other way around: My way doesn't work for you.

That's alright. Cheers!

Thread Thread
 
nubuck profile image
Barry Buck

The opposite of monolithic is modular. A collection of features adhering to an enforced standard that are declaratively composed together and configured to operate as a whole.
I don't consider microservices or micro front ends a great way to build modular solutions as extreme discipline is needed where standard adherence is not built in.
Composition is a design pattern I believe best suited to declarative stacks like React and Feathers for example but not great for OO stacks like Angular and DotNet Core.
So, to your point, horses for courses, choosing the right architecture and convention for your stack choice is as important as tech choice for your solution - which is ultimately influenced by team experience and preferences

Thread Thread
 
webjose profile image
José Pablo Ramírez Vargas

Ok, so we agree. Trying to bake a one solution for all is not good.

Couple of things:

  1. Search for "monolithic software". Looks like you are on the minority side, hehe. You may be in the very few that uses "monolithic" as an antonym to "modular".
  2. Don't be harsh on microservices. Just because they require discipline doesn't mean is bad. Is great. I am in my second enterprise-level microservices project. I enjoy it every second.
Collapse
 
menard_codes profile image
Menard Maranan

Agree to this 👍

Collapse
 
chema profile image
José María CL

Really? Based on what?

Collapse
 
nubuck profile image
Barry Buck

Based.

Collapse
 
urielbitton profile image
Uriel Bitton

That would be one hell of a mess lol !

Collapse
 
nubuck profile image
Barry Buck

Write enough commercial products at scale and it'll make sense. As products and customer services grow, feature driven conventions tend to emerge organically as more members join to form service focused teams.

Collapse
 
dikamilo profile image
dikamilo • Edited

Your architecture is not scalable for big projects. It will only cause big mess when you will have tons of features, and your store, services, helpers, hooks etc. will have a hundred of files. All will be highly coupled.

Also, helpers, utils are antipattern, you should avoid this. Since this is a big bag for everything.

In that case, consider Feature-Sliced Design or monorepo with separate packages per feature/layer managed for example by nx.dev

Collapse
 
seanmay profile image
Sean May

This is the MVC pattern, just with more folders, and less abstract names.

The problem with the MVC pattern at a system scale is that on large enough projects, you are going to have thousands of files in each folder. All of the files in that folder are completely unrelated to one another. You start relying on naming practices like

/services
  /feature-a.service.ts
  /global-n.service.ts
  /system-x.service.ts

/templates
  /feature-a.template.tsx
  /feature-b.template.tsx

/styles
  /feature-a.style.scss
  /feature-b.style.scss
Enter fullscreen mode Exit fullscreen mode

It becomes intractable, after a few dozen files, to figure out which files you need to change, while working on a new feature, or while hunting for a bug.

It also makes migrating / refactoring miserable, because you don't know what you can move from where. Something insidious also starts happening; developers who want to get something over the line start reaching into other features’ files, for their exports, because there is no boundary there, no matter how artificial... and instead of factoring the code out, into a shared dependency, they'll keep it where it is, and use it as-is, and woe be unto you, if they modify it, in place, for their needs.
There's nothing you can really do to prevent that, either.

In component/plug-in/module/onion/honeycomb/etc architecture, the ideal is to separate code by feature; colocating all of the services, styles, templates, tests, et cetera, which that feature needs to function. To the point where, if you really wanted to, you could publish each feature as an npm module. Don't; the overhead of managing dozens of releases is intense, unless you need to... but you could if it is warranted. Internally, if you want your feature to have an MVC folder structure, you can knock yourself out. But all problem solving or code additions, tests and the like are localized to either that specialized folder, or its explicitly listed dependencies. /features/a/ should never, ever reach into /features/*/ for anything, and common dependencies should be factored into their own space ("/shared/", "/common/", "/my-app/", or "npm i"... whatever). The imports not reaching into the internals other features/components can even be checked statically, depending on the tool, versus relying on human code reviewers to be scrutinizing each filename and import statement.
You could independently document each feature, with a readme, or a storybook, or both.

Then it's just up to the app to instantiate the pieces with runtime dependencies and app-specific parameters, and tie everything together.

It's not perfect, and publishing each component, or running a massive monorepo have their own drawbacks, but you don't need to go that deep to reap the benefits.

Collapse
 
romeerez profile image
Roman K

MVC is an abbreviation with quite a clear meaning, not every top-level structure should be called MVC, and at the same time, feature-based structure can be MVC.

For example, in my home directory I have "images", "videos", "downloads", "documents", it is a top-level folder structure, but it's not MVC. And, for example, if I have multiple feature folders each of which contains "model", "view", "controller" files it is MVC.

I was working with MVC framework in other language, and I keep wondering why does it lost its meaning in JS world? Do you think MVC is a synonym for top-level folder structure?

Collapse
 
seanmay profile image
Sean May • Edited

No, I don't think that Model View Controller is just folder structure. Nor do I think Model View View-Model (a Microsoft variant, essentially based around ActiveRecord), is just top-level folder structure.

Rather, I think that a lot of people blindly adhere to patterns without asking why, or without questioning the pros and cons of the pattern, themselves.

And thus, when I see someone talking about the potential of having hundreds of files all jammed into a folder that have nothing in common, except for how they are used in a conceptual, hand-wavy cognitive framework, it instantly trips alarm bells which are also tripped for me, while being brought in to consult on other codebases, which happen to be MVC/MVVM... and funny enough, most of the criticisms still apply.

Note you didn't say anything to address any of my issues, which apply regardless of code style (but are worse in mutative OO, due to the nature of mutation), and also note that the code itself does not need to be architected expressly in an MVC/MVVM fashion, for my concerns to apply, directly.

Also, if I cared more, I would take umbrage with "MVC has a clear definition" the number of people arguing in the early 2000s about whether it was fat models, or fat controllers, or whether Views had code or needed "code-behind" view bindings that were separate from the view (eg: ViewModel), or if services should be separate from Models or Controllers, or they should be the same code... and into ORMs, and if they counted as Models, or Repositories, abstracting ORMs...

Yeah, I’ve been through the people following Fowler and Feathers and Evans and Martin and Beck and the like. And I have seen a lot of the followers blindly applying patterns, arguing over whether a domain model was or was not anemic, if the model didn't balloon to thousands of lines of code, to encompass literally every possible type of thing you could do to validate, use, save, convert, transmit, or exponge what would otherwise be a struct. And yeah, I like a lot of those books. Refactoring, and Clean Code and Domain Driven Design and the rest are really good books. But lot of the advice in each conflicts with advice from others... and yet, remains good advice, in context. In context. That's the key. Context. Your suggestion is for everyone to follow this folder structure. That's context-free. Just like the blind adherents to MVC.

Thread Thread
 
romeerez profile image
Roman K • Edited

I'm totally sharing your frustration about all of these.

Less experienced people don't have the answers to "why" so they have to trust to others. Overexperienced people may be injured by bloody enterprise and arguing about rich models vs anemic. I agree that asking "why" is the key, and ability to summarize pros and cons is what makes you a really good developer. But it's complex, you know. I have own favorite approach (three tier I guess), but it's hard to say if it become a problem on a specific project.

MVC is just a model view controller, it doesn't say where to put logic, it's bearable on small projects and unsuitable for bigger. My point was that people misuse it in a different meaning.

This is the MVC pattern, just with more folders, and less abstract names.

I agree this post teaches beginners to do thoughtless things they could just do themselves, but the only letter from MVC it has is "views". "React is a JavaScript library for building user interfaces" - so it always has a view layer by definition, it doesn't make it MVC.

Thread Thread
 
seanmay profile image
Sean May • Edited

The MVC problems that people have with MVC aren't that the parts of code are called "M" and "V" and "C"...

They could be called "X" and "Y" and "Z" and it would still be the MVC problem.

Like an "XY" problem is when you try to go three steps too far in the wrong direction and are looking for some ridiculous solution to a huge mess, when you could just reverse to X, and go in the correct direction and all of your problems would disappear. That's the XY problem. It doesn't matter if the variables are actually called X and Y.

The organizational problems of MVC aren't even code-related, let alone MVC code specific. They are the MVC organizational problems, because the problems are embodied by the MVC pattern.
MVC has other issues, but all of my criticisms have been about MVC organizational issues (that being grouping everything horizontally, and then flattening many of those horizontal layers down, and stuffing anything that could possibly be shoehorned into one of those layers into the same folder). The problem is the same, regardless of whether the folder is named "M" or "m" or "model" or "Model" or "models" or "Models" or "Bob".

Thread Thread
 
romeerez profile image
Roman K

No, not every random structure can be called MVC.

Thanks for letting me know about XY problem! Now I can use this.

I think you're trying to prove that horizontal structure is bad because of MVC.

X = horizontal structure is bad
Y = MVC

You're trying to prove X because of Y! That's a XY problem.

Thread Thread
 
seanmay profile image
Sean May • Edited

Not all horizontal structure involves sticking all code in that layer in exactly 1 folder.

Lots of horizontal structures might have many folders for that layer.

Your structure has exactly 1 folder per layer. MVC has exactly 1 folder per layer.

The problems I am complaining about arise from the cross section of:

  1. horizontal architecture
  2. having exactly 1 folder per layer

That is why it's the "MVC" problem, and not the "all horizontal architecture" problem; because MVC exemplifies the problem of having exactly 1 folder per horizontal layer.

Thread Thread
 
romeerez profile image
Roman K

Your structure has exactly 1 folder per layer. MVC has exactly 1 folder per layer.

It's a coincidence, not a MVC pattern or MVC specific problem. Nobody is forbidden to restructure MVC as they like, there is no such rule to have 1 folder per layer in MVC, it's just the way people do it usually because they suppose it's simpler.

Thread Thread
 
seanmay profile image
Sean May • Edited

It's not a coincidence, it is an antipattern that is replicated by a lot of MVC frameworks / authors / evangelicals / etc.

That's not accident, it's cargo cult.

Thread Thread
 
romeerez profile image
Roman K

I assume you, like anybody else, have a home directory that is structured in a similar way. Do you have "Images", "Videos", "Downloads"? When your browser downloads something, it saves it to "Downloads", and doesn't create some folder by feature or domain or something.

This is a natural way to structure files. Requires no thinking. Structuring files in a different way requires a little thinking.

So you have to agree it's not an MVC pattern but a natural way of structuring files (which doesn't work well in software, but anyway), or please explain why your home directory follows MVC pattern, are you a cargo cultist?

Thread Thread
 
seanmay profile image
Sean May

I don't actually use the general folder structure.

If I am making a video, raws are stored by date, by camera, not just "video".

Then separate pools/folders are made for clips, based on intent (feature), not just put back in "video".

Thread Thread
 
romeerez profile image
Roman K • Edited

You have won at this pointless arguing! 🎉
My sincere congratulations!

Collapse
 
davidescobar007 profile image
J. David Escobar

Really good article, I would change the name from services to utils. It just makes more sense to me. Services makes me think it's something related to the api layer. Also, I have seen utils folder in many large complex enterprise code bases

Collapse
 
webjose profile image
José Pablo Ramírez Vargas

While ordering code files does go a long way, I don't really think there's a single recipe that can be applied everywhere.

Collapse
 
shifi profile image
Shifa Ur Rehman

Everyone who makes posts like these share their experience and their experience alone. In my honest opinion, there should be a standard way of arranging the files around which every developer can make minor modifications suitable to their needs and practices.

If we think from a standardization's point of view, we already get a standardized way of arranging the files in any project. We usually build on top of that given skeleton to add up things we are going to use - and discard what we don't need.

Arranging the files in a project can be a recipe of disaster, in my honest experience, if the developers just chose standard "names" for the folders instead of going their way and forcing everyone else in the line their way, it doesn't matter how you organize the folders. Jose has given an acute example of this scenario. Instead of calling the folder services, calling it utils feels much more robust and standard way of naming it. I mean the big dawgs are doing it so why shouldn't we?

For me, it's just a way of looking into the soul of a developer when i read articles like these. There is absolutely nothing wrong with such articles, only when someone starts imposing their way onto the general community.

A big drawback of imposition is that mostly these articles are read by novice developers and they pick up habits that deter them from climbing the ladder in this already challenging market.

But it was a good write. Me likey ^^

Collapse
 
wilmela profile image
Wilmela

Sure

Collapse
 
wilmela profile image
Wilmela

Absolutely.

Collapse
 
theme_selection profile image
ThemeSelection

Very Informative..!!

Collapse
 
potcode profile image
Potpot

I would recommend another architecture: screaming-architecture-evolution-o...

Collapse
 
wilmela profile image
Wilmela

Nice write up.

Collapse
 
harmonygrams profile image
Harmony

Thanks so much for article. I really appreciate

Collapse
 
artemean profile image
Andrii Artemenko

Such structure becomes maintenance nightmare once the project grows bigger than "to-do list" and more than two people working on it.