With the rapid emergence of new front-end technologies and the increasing demand from the product for more complex UI and UX, it has become harder to maintain and manage logic in the front-end world. In a simple application with a simple product that doesn’t have complex requests from the front-end, it’s easy to manage a lot of your code in single files or at best, split them based on their main domain.
But, at Fiverr, recently we’ve ended up with a number of large files containing thousands of lines of code that are not only very hard to manage, but also to maintain.
In this article we’ll discuss what folder by type/domain is, why it’s so hard to maintain in a large-scale application, and we’ll present a better approach along with the pros and cons of that approach.
Folder By Type/Domain
├── javascript │ ├── user.js ← — — feature #3 │ ├── order.js ← — — feature #2 | └── gig.js ← — — feature #1 ├── css │ ├── user.scss ← — — feature #3 │ ├── order.scss ← — — feature #2 │ └── gig.scss ← — — feature #1 ├── spec │ ├── user.js ← — — feature #3 │ ├── order.js ← — — feature #2 │ └── gig.js ← — — feature #1 ├── images │ └── user_default.jpg ← — — feature #3
In order to work with that structure, you’ll have to work in 3 different big folders. In a large-scale application, this creates a number of problems:
- Not scalable — once you limit your folder structure to a type, that type can easily exceed a reasonable amount of files and become hard to maintain — in large scale applications you can easily end up managing 100 different css/js/whatever-type files.
- Not modular — with the help of module bundlers like webpack, browserify and more, we can create smaller modules/files and consume them in different areas of our code.
- Hard to work with — let’s say you want to delete a feature. If the feature is coupled and written inside a large file, it’s harder to remove or even refactor.
- Leads to large files — since that structure doesn’t encourage modular code, we have a tendency to end up with large, monolithic-style files that are both hard to refactor and to work with.
- Prone to conflicts — in large applications you have large teams that within such an architecture might work on the same files, resulting in confusion, conflicts and far more communication than necessary.
For small-scale applications, a folder structure like the one above can make sense — but if you plan to go big, and scale, you should consider the next structure instead.
Folder By Feature
Since we aim to develop a large-scale web application, used by many different teams and developers, we need something modular, easy to maintain, and easy to navigate.
├── gig ← — — feature #1 │ ├── gig.js │ ├── gig.scss │ ├── gig.spec.js │ └── index.js ├── order ← — — feature #2 │ ├── index.js │ ├── order.js │ ├── order.scss │ └── order.spec.js └── user ← — — feature #3 ├── index.js ├── user.js ├── user.scss ├── user.spec.js └── user_card ← — — feature #4 ├── index.js ├── user_card.js ├── user_card.scss ├── user_card.spec.js └── user_default.jpg └── user_default.jpg
The advantages of this structure are:
- Highly modular — modular code is code that easy to maintain.
- Architecture by product — if we split our code based on features, it’s easy for us to own/share/split the code between different teams. So, not only does our code reflect a large-scale client web application, it also reflects large teams, groups etc.
- Easy to refactor — since our code is written in small chunks it’s easier to refactor in the future. Good code is a code that can be refactored in the future — bad code is a code we can’t refactor and will need to rewrite from scratch.
- Team friendly — since we have lots of developers from different teams, we need them to work in relatively isolated environments without any code conflicts.
- Reduce unnecessary communication — it’s very unlikely that different teams will work on the same files, or change the same functions or modules, since the folder structure encourages developers to write self-contained, modular code.
As with everything, there are some cons to this structure as well:
- Harder to learn — it’s easy to make structural mistakes when your application is not limited to types — when you have 2, 3 or 4 main folders to work with, it’s easier to know where to place your newly created files.
- Nesting hell — you can easily find your application containing 20 levels of folders — so you always need to make sure that you build your application as flat as possible, even with a folder by feature structure.
How To Name Folders/Files
If you pick the folder by feature path, it’s easy to fall into a trap and name your folders or files with generic names like: input, button, components, actions, reducers etc. But if you use those names, you need to be aware of their limitations. For example, the names aren’t descriptive, and they’re opposite to the concept of folder by feature: “components”/”input”/”button” aren’t features, they’re generic modules or components.
First of all, we need to remember that our example above lives within a greater application, so within your application you should have a global “input” or “button” that every other corresponding element should inherit from (“input” should come from a 3rd party internal/external library).
Real Life Example
So let’s try and learn by example. Here we have different examples that will help us understand how we take into account a variety of concerns with this folder structure:
- Data flow (redux — where we create module, connect or react state)
- Component and sub components
- Writing tests
- Styling
- Reusable/shared components
- Flat design structure, avoid nested hell
Example #1: Reviews Application
Our first example is Fiverr’s review application — we use it in the user page and in the Gig page.
As you can see, we can connect to redux store from any point of our application. And our application accesses global files like “index.js” and “reviews.scss”.
- ReviewList is where we assemble our application, where we can connect to redux and export most of our application.
- Down the road, once our product complexity grows — we can add new features — in this case, a folder with redux connect and module etc.
Here’s is our example structure:
├── LoadMoreButton.js ├── LoadMoreButton.scss ├── ReviewList.js ← Here we construct the app ├── ReviewList.scss ├── ReviewList.spec.js ├── index.js ← Here we simply import ReviewList component and export ├── module.js ← LoadMoreButton fetch side effect is here ├── module.spec.js ├── review_list_header │ ├── Filter.js │ ├── Filter.spec.js │ ├── ReviewListHeader.js │ ├── ReviewListHeader.scss │ └── index.js ├── single_review │ ├── NoReview.js ← NoReview can be its own folder sibling to SingleReview (#6) │ ├── NoReview.scss │ ├── NoReview.spec.js │ ├── PreviewImage.js │ ├── PreviewImage.scss │ ├── PreviewImage.spec.js │ ├── SingleReview.js │ ├── SingleReview.scss │ ├── SingleReview.spec.js │ └── index.js └── user_rating ← Big enough to be its own folder ├── UserRating.js ├── UserRating.scss ├── UserRating.spec.js └── index.js
We have flexibility in regards to the structure of our application, but we do have some guidelines. For example:
- We should make sure our structure is as flat as it makes sense to be — which is why we don’t always create a new folder.
- We should create a folder for our component when it make sense, e.g. when the component has some complexity, when the product is expected to grow, or when we have more than one component under a component (i.e review_list_header has Filter and ReviewListHeader, Which is why we created a folder for review_list_header).
- You can create a new folder if otherwise you’ll end up with too many files, but again creating a folder for every 2–3 files can be pointless, so it does need to make sense within the structure of your application.
- If your component has a concern like, connecting to redux store, you can create a new folder.
- Use common sense — if your root folder has too many files, group them together based on the structure of the DOM/product.
- NoReview can be its own folder sibling to SingleReview, but, product wise, noReview is a single review which allow us to group those two together and avoid another folder.
Example #2: Payment Account Settings
Payment Account Settings is another feature that we’ve built this way. Below you’ll find some screenshots of the UI and at the end you’ll be able to see a detailed diagram of the structure of the application that includes side effects, components, reducers, actions, connect and more.
Summary
Not everything is written in stone, there are no strict rules — but more of a string of common sense do’s and don’ts. We don’t want folders with too many files since it doesn’t make sense to manage so many files within a single folder, We don’t want a structure that is too deeply nested since it’s hard to maintain and navigate around in. We want the code to be easily accessible & consumable by the code & components that surround it.
Lior Amsalem embarked on his software engineering journey in the early 2000s, Diving into Pascal with a keen interest in creating, developing, and working on new technologies. Transitioning from his early teenage years as a freelancer, Lior dedicated countless hours to expanding his knowledge within the software engineering domain. He immersed himself in learning new development languages and technologies such as JavaScript, React, backend, frontend, devops, nextjs, nodejs, mongodb, mysql and all together end to end development, while also gaining insights into business development and idea implementation.
Through his blog, Lior aims to share his interests and entrepreneurial journey, driven by a desire for independence and freedom from traditional 9-5 work constraints.
Leave a Reply