Would you like to buy me a ☕️ instead?
When starting a new project or refactoring an existing one, the question often arises: how to set up the project’s directory structure. My first advice is to keep the folder hierarchy as flat as possible for as long as possible. But depending on the size of our project, there might be a time where we feel the need to add additional folders to organize our Vue components and other files.
In this article, we explore three variants for structuring our Vue project. From a very flat file tree to creating separate directories for each domain module.
Flat File Tree
In the following example, we see a nice, simple, and flat directory structure. I highly recommend you not make things more complicated than that as long as you don’t feel any pain stemming from the flat directory structure.
/my/project/src
├─ components
│ ├─ BaseButton.vue
│ ├─ BaseCard.vue
│ ├─ BaseLink.vue
│ ├─ ...
│ ├─ ProductCart.vue
│ ├─ ProductDetail.vue
│ ├─ ...
│ ├─ TheFooter.vue
│ ├─ TheHeader.vue
│ └─ ...
├─ services
│ ├─ product.js
│ └─ ...
├─ utils
│ ├─ as-array.js
│ ├─ format-price.js
│ └─ ...
└─ ...
Your first instinct might be that you will never be able to find anything as soon as you have more than 100 components. If that’s the case, I suppose you don’t use your editors’ quick-open feature to open files. However, I highly recommend you make it a habit to use the quick-open command to open files (on macOS in Visual Studio Code, the keyboard shortcut is CMD+P
) instead of searching them in your editors’ file tree. As soon as you have internalized to open files with quick-open, you’ll notice that you barely look at the file tree anyway.
Flat File Tree with Base Directory
When things get more complicated, I still recommend increasing the complexity only gradually. For example, if the utterly flat directory structure does not work for you (anymore), start by adding only one additional directory: base
.
/my/project/src
├─ components
│ ├─ base
│ │ ├─ BaseButton.vue
│ │ ├─ BaseCard.vue
│ │ ├─ BaseLink.vue
│ │ └─ ...
│ ├─ ProductCart.vue
│ ├─ ProductDetail.vue
│ ├─ ...
│ ├─ TheFooter.vue
│ ├─ TheHeader.vue
│ └─ ...
├─ services
│ ├─ product.js
│ └─ ...
├─ utils
│ ├─ as-array.js
│ ├─ format-price.js
│ └─ ...
└─ ...
The base
directory contains all the generic and highly reusable components of your app. Apart from keeping your component directory a little tidier, moving all base components into a separate directory solidifies their status as very generic and reusable components.
Group By Domain
The above approaches are sufficient for most applications limited to one or a few specific use cases. But suppose we need to build a full-scale enterprise application with dozens or even hundreds of different domains. Then, we can decide between two strategies: one of them is that we can choose to go the Microfrontend route. Thus, we slice our application into multiple, separately deployable applications. But depending on the resources we have, this might not be a realistic scenario. Furthermore, there are a lot of things to consider when building a full-scale Microfrontend architecture.
But we can get many of the same benefits by applying the Domain-Driven Design (DDD) principles when planning our application’s directory structure.
/my/project/src
├─ components
│ ├─ base
│ │ ├─ BaseCard.vue
│ │ ├─ BaseButton.vue
│ │ ├─ BaseCard.vue
│ │ ├─ BaseLink.vue
│ │ └─ ...
│ ├─ TheFooter.vue
│ ├─ TheHeader.vue
│ └─ ...
├─ modules
│ ├─ product
│ │ ├─ components
│ │ │ ├─ ProductCart.vue
│ │ │ ├─ ProductDetail.vue
│ │ │ └─ ...
│ │ ├─ services
│ │ │ └─ product.js
│ │ └─ utils
│ │ └─ format-price.js
│ └─ ...
├─ utils
│ ├─ as-array.js
│ └─ ...
└─ ...
Now we have separate directories for all the different modules (=domains) of our application. So think of every module as a tiny app inside our app.
One thing to watch out for is to keep dependencies between different modules at a minimum. Ideally, we manage to avoid modules importing code from other modules altogether.
Structuring our application that way makes it easier to extract parts of it into separate Microfrontends later. It also makes it easier to find stuff if our application grows to thousands of files and components.
Refactoring, not only of our code but also our directory structure, should be a constant. Suppose we notice that multiple modules often change together for the same reason, or we regularly need to import code from one module into another module. In that case, that is a hint that we should combine them in a single module instead.
Working with module directories has many benefits but don’t fall into the trap of premature optimization. For example, it might not always be clear to which domain a certain functionality belongs when just starting.
Do you want to learn more about advanced Vue.js techniques?
Register for the Newsletter of my upcoming book: Advanced Vue.js Application Architecture.
Wrapping It Up
Always try to keep things as simple as possible for as long as possible. An almost entirely flat directory tree is the most straightforward way of how to structure a Vue application. Although, some people recommend using module directories to structure our projects right from the beginning because, so they argue, at some point, you wished you did. While I can understand that point of view, in my experience, we don’t always have a clear picture of which functionality belongs where when starting a new project. Although refactoring our directory structure at a later point can be painful, in my experience, it is worth it. It’s a productive pain because it tells us something important about slicing our application most efficiently.