Architecting Frontend Projects To Scale
Michael McShinsky
Posted on July 6, 2020
I, like any other web developers and engineers, prefer to make my work life as sane as possible. Having structure not only keeps life a little more pleasant, but is critical to scaling and even allows creativity to flourish! Having a clear structure and consistent plan in my code keeps me performant, allows me to better plan for scaling, avoid unnecessary refactoring code sessions, and understand the app hierarchy without having to re-learn each component or service every time I need to change or upgrade features.
Pretty much most developers who start out with most JavaScript frameworks use a built-in CLI (command line interface) that another team has built for the said framework in order to jumpstart the development process with minimal effort. Nothing is inherently wrong with this approach and this saves developers a lot of time from the first wave of configuration bugs. The next step after setup is to build out your code structure. Everyone, without a doubt, has opinions on this and will strongly defend their approach. I too have molded my own architecture that fits my projects as they grow.
In this article we'll use the create-react-app starter structure as a base configuration that anyone can start with when following along with this article and not get lost.
What this is and isn't
This is a dive into project structure and package opinions. This is not a comprehensive study into the "musts" and "must nots" of what libraries and packages to use and avoid. Your project is a living structure! My views and opinions apply to the issues you face may or may not have merit depending on the needs of your end users or development team. Hopefully, this article will provide another valuable perspective into keeping yourself and teams organized when working on small and large projects.
Basic Configuration
So you don't have to dig through links and websites, here is the create-react-app
document structure you will see when starting out.
my-app
├── node_modules
├── public
│ ├── favicon.ico
│ ├── index.html
│ ├── manifest.json
│ └── robots.txt
├── src
│ ├── App.css
│ ├── App.js
│ ├── App.test.js
│ ├── index.css
│ ├── index.js
│ ├── logo.svg
│ ├── serviceWorker.js
│ └── setupTests.js
├── .gitignore
├── package.json
├── README.md
└── yarn.lock
I have a decently strong opinion about the src and components folder. Do not use the src or components folder for everything! This doesn't scale for larger applications and it is super annoying to dig through giant flat lists of .js files when you want to find a particular piece of code.
Uppercase versus Lowercase
Let's get this one out of the way quickly. React developers likes to define component specific files as uppercase based on said conventions from popular developers in this space. If you are on someone else's project that is uppercase, don't go changing all the file names. A standard has been set and there is a right time and a wrong time to approach changing an individual project's standards.
On the other hand, if you are going to be working on my project, lowercase file names is the standard we'll be living by. It makes sense, easy to read, and pretty much all other project types and frameworks use this approach. Also, hyphens between words is a must even if it makes it a tad bit longer than you are normally comfortable looking at.
New Frontend Architecture
If you are just looking for an example for this article of how to structure your next project, I will link a simplified version of it to you right here. If you want to keep reading about why it is structured this way and understand the purpose of this architecture is, please, continue reading. We are going to adopt a MVVM architecture to manage our project.
Most of your projects starting out are probably going to be 100% frontend based working with an internal or external api or a separate data source that isn't bound tightly to the frontend code. Our architecture may alter, for example, if we were to structure our project with server side rending in mind. Let's take a birds eye view of what we have in our new app's folder.
my-app
├── assets
│ ├── images
│ ├── scripts
│ └── styles
└── src
├── components
├── constants
├── models
├── routes
├── services
├── views
├── utilities
├── index.css
├── index.js
└── serviceWorker.js
index.js
In the provided example, index.js is used heavily to export multiple components or represent the parent components (container) of a view or shared element.
Assets
Let's break assets down a bit and understand what is going on:
assets
├── images
├── scripts
└── vendors
└── styles
└── vendors
The assets folder within the src folder is usually located here to represent internal only resources that should not be readily available to the public as a stand alone, linkable, or downloadable resource. Pdfs, downloads, blog post images, etc... could be stored instead in the public folder for mass distribution.
I'm not going to recommend a specific sub structure for images. I don't even have an defined opinion except probably keeping images grouped by pages, features, layouts, and specific use cases. Scripts will usually be third party libraries that don't have natural integration (import/require) into your project and you are okay with them living in the start or end of the body of your html document. The same goes for the styles folder.
The reason there is a vendors folder is because it is much easier to handle internally written files that live in the scripts and styles folders in the base folder while external/third party libraries will live in the vendors folder. This will make it much easier to reference visually for team members and even add associated overrides (if your can't modify the main libraries file due to possible future updates), e.g. bootstrap.min.css, bootstrap-overrides.min.css. Not ideal for some... but it is organized and easy to refer to. Remember, scripts and styles are primarily meant for third party libraries that will not be living within your main projects JavaScript documents and stylesheets.
Components
We are going to keep the components folder because I still believe that it is important. It's use should not be to hold your project but rather to hold components that will be shared throughout your project. This includes: layouts, private, public, templates, sidebar, header, etc... What ever you want that will be used more than once by multiple modules or views.
components
├── buttons
├── forms
├── layouts
├── partials
├── private
│ ├── header
│ ├── sidebar
│ ├── card
│ └── modal
├── public
│ ├── header
│ ├── pricing-tables
│ └── footer
└── shared
Note that I like to divide components that whose sole purpose belongs to the customer facing website or the user facing app by public and private. They could also be names website and app or you could keep all components folders on the same level under components. All that matters is giving a home or primary location to reusable components for your project. When it comes to the plurality of folder names, I am still undecided due to the high variable use cases of component naming.
Models and Services
Let's bundle these together. Using a MVVM approach as inspiration, models will hold constructors that will mold incoming and outgoing server data into repeatable and scalable objects. Services will hold the generalized and specialized functions that send this data back and forth between the client and the server. Services will also hold state based solutions like redux configurations or global context.
├── models
│ ├── client.js
│ ├── product.js
│ └── task.js
└── services
├── context
├── redux
└── api
├── clients.js
├── products.js
└── tasks.js
Constants
Anything that will be referenced globally in the app should be stored here. This can include:
- Unique IDs from a database (id or guid).
- Config values for difference api services that aren't part of an .env file.
Note that this folder could be substituted for .env file(s) holding all information if it is considered dynamic based on how your hosting is configured or company policy is enforced.
Utilities
Utilities can be one or many files that define small utility functions that your app will utilize. This may be things like specialized dates, formatters, or one use functions that are needed often but don't belong to any one component or module in your project.
Routes and Views
Most likely, most of your day will live between here and components, putting together the finalized code passed from designers to you for implementation. You have already written models and services to consume the data from the server, and now you need to utilize it. A basic view structure may look like the example below.
Putting routes into their own folder has been something newer for myself. Keeping routes together and importing the views for the routes has made it easier to manage how my more recent apps change as business requirements evolve. This is more of a personal preference than an insisted pattern for others to use.
routes
├── components
│ ├── private.js
│ ├── public.js
│ └── index.js
├── index.js
views
├── private
│ ├── clients
│ ├── dashboard
│ ├── products
│ ├── tasks
│ └── index.js
├── public
│ ├── about
│ ├── auth
│ ├── home
│ └── index.js
└── shared
Once again, I like to make sense of how my projects is structured visually by separating the public facing website and customer facing internal app. Each of these view component folders is where the view for a route is defined.
client
├── index.js
├── client-redux.js
├── client.scss
├── client-styles.js
├── tests
├── components
│ ├── modal
│ └── // unique components for view
clients
├── clients-redux.js
├── clients.scss
├── clients-styles.js
├── index.js
├── tests
└── components
├── modal
├── list-item
| ├── list-item.scss
| ├── list-item-styles.js
| └── index.js
└── // unique components for view
This example contains a range of possible files you may be using in your project. We also break out unique child components to the view that wouldn't make sense to have in our shared component folder by keeping them inside the view's component folder. By adopting a heavy view approach that contains just about everything related to the view, we can utilize maintaining new and old code as it is implemented and deprecated. This allows us to be lean and agile in our development cycle. We also avoid developer code and pull request overlap as different developers work on different features.
Conclusion
With that, we have defined the general outline of a more scalable and maintainable architecture. To an extent, this architecture is agnostic to your frontend libraries and is meant to be modified to the needs of your team. As projects are living and ever changing organisms, and I am fallible, please let me know if I am missing anything. What are your favorite or preferred approaches to frontend structure? Let me know in the comments below. I would love to hear from you!
If you want a starter version of this, a link has been provided here: React-Starter
If you found this helpful or useful, please share a 💓, 🦄, or 🔖. Thanks!
Posted on July 6, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.