How to build a scalable folder structure for a nextjs app
December 11th, 2022I think one of the most common problems, at least in the javascript ecosystem, is the folder structure. I've seen countless articles of the best approach for scalability, small apps, large apps, you name it. Over the years i've used a lot of them and i always felt there was something missing. In recent times i have changed the approach and instead of trying to get the right folder structure, i try to get the good enough folder structure, which for me is one that keep things organized and is flexible enough to allow me to change it in the future.
This is my journey across different folder structures over the years and at the end i'll explain the one i am currently using which i've found to be quite good.
Codebases usually are structured either by type (components, reducers, factories) or by page (account, profile, products). Let's quickly review them (if you're already familiar with both approaches feel free to skip this part).
Structure by type
I found this to be the most common in small apps (and it's the one i used the most in by first years of development). It's easy, it's simple, you don't need to think about features. But it has shortcomings, specially in large applications with many pages or areas: name collisions, difficulty to find you're way around the codebase (specially when you're new to it) and high cognitive load are some of them.
src/
components/
reducers/
filters/
tests/
Structure by page
Structuring your codebase by page is very common in larger applications, and it's the one i saw as a natural evolution of the structure by type. In this case, you first split your codebase in pages and normally inside it you split things by type (which in the context of a page it's usually manageable).
The problem with this approach is that in any relatively large application (and possibly small-ish one) it's very likely there will be some UI and logical component that needs to be present in both. If you have an <Thumbnail/>
component in the profile
and products
page, where should be the source file located? This normally ends up in having a common
folder that it's outside of any page breakdown, and as the codebase grows larger, this folder becomes a to place everything that you can't place in any page folder, regardless of whether that component/thing is actually common to more than 2 pages.
Atomic design
Some time ago i was starting a new project at my company and i was thinking about the folder structure, trying to move away from the structures i had used, and i remembered about atomic design by Brad Frost, something i had read about years ago (if you're unfamiliar with atomic design i'd just say that is a methodology of naming and structuring the parts of a design system, and i'd also highly recommended you read about it in Brad's website).
I thought that creating a folder structure based on it would be a good idea. I tried a few different options and this was what i came up with
src/
components/
atoms/
molecules/
organisms/
templates/
pages/
As defined by atomic design, atoms and molecules are building blocks of more complex components, which are the organisms. A group or organisms (and maybe some molecules) create a template and a template plus data is a page (as my project was in next, i already had my pages
folder).
I used it for some months and it worked better than other structures, but as i created more components, i realised that i was spending a lot of time thinking whether a component should be an atom or a molecule, or what was the line between a complex molecule and a simple organism. This structure created a lot of gray areas that forced me to think about things i didn't consider important. I just wanted a simple structure that didn't have gray areas (not as many, at least) and was flexible enough i could change it if needed.
A simplified atomic design
Please note that when i say "a simplified atomic design" i'm merely referring to a simplified folder structure based on atomic design and by no means i'm implying that atomic design is complex or is not simple enough. I think that atomic design is great for what it was designed for (design systems) and not for a whole next app as i intended to use it.
What i realised after some months of use was that, first of all, i didn't really care about whether a component was an atom or a molecule, in my use cases i could always use the name interchangeably. What i did care about was the function they always had: they were generic building blocks that didn't have any business knowledge (think of a button component, an accordion component or a wizard-like component). The second thing i realised is that despite the name, there were just two more functions that components had: they were either a page or the functional parts a the page was made of. Those functional parts, unlike the components from before, were not generic, they always had business knowledge and they only made sense within that context (think of a login form, a profile menu or a list of FAQs). Some of them were very simple and some very complex but they all had the previous criteria in common.
For the last type of components, pages, i decided to used them as typical nextjs pages: they would be in charge of fetching data (client or server side) and pass it down to the many functional parts.
I've called the first type of components building blocks
: they are generic, business agnostic and can be used anywhere (even within other building blocks)
I've call the second type of components domains
: they have business knowledge and they only make sense within that business context. They can be as complex as required and can use any building block but they cannot use a component from another domain because if they do, that component being reused breaks our rule of only making sense within the context they're in. When i found myself in a situation like this, it always meant that i was breaking things apart incorrectly, either because i was splitting things that should be only one domain or because i wasn't extracting some generic logic into a building block for the two domains to use.
My current folder structure is something like the following (of course, it's a simplification). As you can see, some domains are simple and some have mane components inside, but they all are only relevant in that context, a PaymentForm
component would not make sense in any other domain.
src/
building-blocks/
Input/
Button/
Wizard/
Grid/
domains/
profile/
Avatar/
Summary/
AccountDetailsForm/
footer/
index.tsx
checkout/
DeliveryForm/
PaymentForm/
Summary/
pages/
libraries/
Does it scale?
Since i started using this structure the application has grown from just a couple of building blocks and domains to dozens of them, and many of them are quite complex.
I've found that structuring components on whether they are business agnostic or not rather than complexity (atoms vs molecules vs organisms) or on whether they are (or will be) reused in another page (using the commons/
folder) simplifies things a lot for me as the business awareness is something unlikely to change.
Also, not structuring components based on complexity gives me the flexibility to have building blocks and domains as complex as i need, and i am free to handle that complexity how i think it's best: i have multiple building blocks and domains that within them have many smaller components, hooks, contexts and even reducers. It's all good because that doesn't change the fact that they either are business agnostic or business aware! And since all the complexity is contained and, by definition, not accessed from outside, i can refactor anytime i want.
Is it bullet-proof?
Not really, far from it. There are a couple of things that are not great. First of all, i think the name domains
is bad as it 's not very descriptive, but it was the best i could came up with (and i think after a while the name doesn't matter as much).
Most importantly, i think the same flexibility of domains can sometimes get out of hand, as a domain will grow and grow. I have not find a solution for that but i am thinking more and more that when a domain grows large to a point where it feels out of control that may be indicating that that domain should be an app on its own.
Having said that, i am quite happy with it. I feel it addresses most of my needs in terms of a folder structure for my apps and it does that without getting in the way.