What is the best way to structure a Vue.js application so that it scales and remains maintainable and extendable the more it grows? This is a question that I've heard on numerous occasions and I think one answer to that question lies in the principle of predictability. When it comes to creating a scalable project you want everything about it to be as predictable as possible.
What exactly do I mean by predictability? At it's simplest, it's the ability to intuitively go from a feature request or bug report to the location in the codebase where said task can be addressed. Furthermore, it's the ability to quickly know what tools you have access to at that location in the codebase in order to complete the task at hand.
Why is this important? Well, like me, you've probably had the experience of inheriting or being introduced to an existing project and then on that first task, opening up the codebase and thinking: "I don't even know where to start!".
You might have even been dealing with a codebase for a while and had the same thought! A predictable codebase alleviates this experience as much a possible, making introducing developers to projects easier and continued work more efficient.
I think it's worth noting here that, while predictability is possible, no project will ever be 100% predictable. Every project, new or existing will have at least a slight learning curve. Also, know that predictability doesn't mean that the codebase or application is quickly understandable as a whole. Many large scale applications are simply too complex for this to be possible and they'll take time to grasp in their entirety. Thus, predictability isn't about seeing the complete finished puzzle but more like knowing the shape of a certain piece and being able to quickly see where it fits. In fact, the nature of a good codebase lends itself to being understandable a piece at a time and shouldn't require its developers to ever have to think about the whole at once.
So how do we accomplish predictability in a codebase? The answer: standards, plain and simple. Maybe that's not the answer you're looking for but it's true. The best way to make anything predictable is to make it follow a set of standards. For example, I can predict with almost 100% certainty that the new full size sheets that I bought just today, will fit my bed even though I've never dressed it with those sheets before. Why? Because of the standard sizing system for bed sheets.
So this begs the question, what kind of standards exist for the Vue.js community at large? I'd say that there are 4 sources of standards available.
While some of these are more officially meant to be standards over others, I think they all provide the opportunity to have some common patterns between projects and developers resulting in more predictable codebases.
Let's start by talking about the standardization that official libraries and popular component libraries bring. While the primary purpose of such libraries is to bring functionality to the table, a side affect of this is that shared standards are adopted. If you use Vue Router, for instance, you're not only benefiting from the functionality of the package itself, but you end up implementing routes in one project much the same way as you implement them in another project and much the same way as Vue.js developers all over the world implement them.
The Vuex library in fact embraces and touts this fact as a top feature calling itself a "state management pattern + library".
This may seem obvious, but there is a point to be made. If there is an existing popular or recommended solution for a problem in Vue.js (and even more so if it's an official solution), I would think long and hard before using something else. I'm just as happy as the next guy, DIYing my own components, stores, etc but often times it really pays off in the long run to use the tried and true solution not just because of the functionality, test coverage, and documentation they offer but also because of the standardization they bring. (And in the world of JavaScript can't we all just use a bit more standardization 😆).
When it comes to choosing to use these more standardized solutions it's important to remember what it is your building. Are you building a scalable reusable component? Then maybe the standard library isn't for you because a new standard library is kind what you're attempting to build. However that's probably not what most of us are building. Most of us are probably building an application and if that's the case then it's probably better to use the standard (or at least semi-standard) pieces that already exist as your building blocks.
When it comes to project standards, file structure is an often talked about topic and while Vue has no documentation specifying a particular structure, it does provide a good starting place from the codebase generated with Vue CLI.
Most of us are probably familiar with this structure and that is awesome! That means we're a step closer to predictability! So, the point here is, don't overthink it. Stick with what Vue gives you out of the box and don't stray from it until you have a (really) good reason.
I certainly think there are additions that can be wisely made here (and we'll talk more about those in a minute) but there is no real reason to make changes to what's already there. With this auto generated file structure we have a predictable place for application assets, pages, components, routes, store logic, and a clear entry point. Don't mess with a good predictable thing.
Now, focusing on the component's directory, the Vue style guide has some further advice for us to make our file structure more predictable. Among other things, the style guide encourages the following when it comes to defining components:
Base
or App
)
Table
or a Button
component.The
TodoListItem
in a TodoList
SearchWidgetInput
, SearchWidgetResultsList
, SearchWidget
Hold tight if these don't completely make sense, there's an image coming in a minute that might help 🙂
Besides these, the full style guide has a number of other standards that will help your project be more predictable to a community-wide audience of developers. I won't regurgitate them all here but highly recommend you read and follow the style guide yourself.
While there are some great standards set in place for the Vue.js community at large by official sources, there are other patterns not so widely adopted that, in my experience, can be just as helpful and made into standards for you or your team's projects. Such standards are necessary as community wide ones aren't 100% comprehensive but just beware and be strict when it comes to how team standards are decided upon and maintained... it can be a rabbit hole of ever changing rules if you're not careful. That said here are some of my recommendations for your Vue.js project standards.
You might have noticed a common thread amongst most of the component rules from the Vue Style Guide earlier. The naming conventions always help group related components together in the file system. Because of this, combined with reasons below, I suggest adopting the standard of a flat component directory. A flat component directory has the following benefits:
post/PostList.vue
, post/PostFeature.vue
, etc)post/List.vue
, post/Feature.vue
) and violates the style guideimport SomeComponent from "@/SomeComponent"
)So what does a flat structure that follows the style guide look like? Here's a good example.
While your large scale application will obviously have many more files, each one is just another component name in a single well organized list so while the scope of the file structure may expand, the complexity does not.
Another practice that makes sense is a standardized way of naming our routes and page components. In your typical CRUD application you have the following different pages for each resource:
While some of these may end up being a nested route (like viewing the single resource in a modal overlay from within the list page), they usually end up having a dedicated route with a corresponding page.
Since I have a background in the PHP framework Laravel, when it came to naming routes and defining their paths in a predictable manner I intuitively fell back on the standards that Laravel already had in place. This made it easier for my Laravel experienced team to more quickly and intuitively work with Vue. Using a "user" resource as an example, the naming convention prescribed by Laravel and adapted for Vue that I recommend is as follows:
Path | Route and Component Name | What it Does |
---|---|---|
/users | UsersIndex | List all the users |
/users/create | UsersCreate | Form to create the user |
/users/{id} | UsersShow | Display the users details |
/users/{id}/edit | UsersEdit | Form to edit the user |
While tempted to name the route in a more traditional Laravel manor like users.index
instead of UsersIndex
, I've found that using the PascalCase works just as well and has the added benefit of matching the component name.
For further consistency and flexibility you should also always reference your routes via their name when using them in router-links and when referencing them programmatically. For example
<router-link :to="{name: 'UsersIndex'}">Users</router-link>
Also it's worth noting that not all routes will fit this pattern exactly as some routes will be more "CRUDdy" than others. For those that don't fit the pattern my only recommendation is that you continue to use PascalCase for your route name for consistency.
Besides the basic file structure that Vue CLI gives you out of the box I suggest standardizing the following for better predictability.
The added directories here are docs, helpers, layouts, mixins, and plugins. You'll notice 4 out of 5 have a fancy icon next to them provided by the VS Code extension Material Icon Theme. That's because at one time or another, for some frameworks or languages, these directory conventions were common enough to get their own icon in the eyes of the extension developer. That's no coincidence!
I've also added a single file globals.js.
So, what's the reasoning behind these file structure standards? Well, I'm glad you asked!
docs
The purpose of this one is obvious but the more important thing is that it's included and is sitting there staring your team right in the face every time they open the codebase. It'll be more likely that certain aspects of the project are documented if the developer never has to leave their IDE. I've also discovered (it was a pleasant surprise) that writing docs first before coding out a reusable class or component usually helps me better design the interface or API of said code. Go ahead, I dare you to give it a try!
Also, besides the docs directory, I've found it helpful to provide a README.md in the root of each standardized directory explaining the purpose of the directory and any rules for what should be included in it. This is especially helpful for those standards that aren't community-wide.
helpers
This is a common directory in many frameworks for basic input-output kind of functions that are reusable throughout the project. They are typically easy to unit test and usually end up being used more than once. I like to start with a single index.js
file and then as the helpers grow, break them up into more grouped files like https.js
, cache.js
, time.js
, etc. Everything in this directory can just be imported and used on demand and if a function ends up never being used at all it can be easily tree shaken from the production bundle.
layouts
I pulled this convention from Nuxt as well as Laravel. It can be handy to not only define page components but also layout components that can be reused across multiple pages. Instead of defining the contents of the page, as the name suggests, these components define more the general layout. For instance, is it a one column or a 2 column page? Does it have a left sidebar or right sidebar? Does the layout include the typical header and footer or is it a completely blank layout maybe with the page content absolutely centered? Usually there are only 2 or 3 of these layout components but nonetheless they can be handy abstraction to have.
mixins
This directory is for organizing all your Vue.js mixins. I think it's important to still append the word Mixin to the end of every file name (like ResourceTableMixin.js
) for easy searching in your file switcher. Though I haven't had the chance to really work on a larger scale Vue 3 project yet, I assume this will probably quickly change to a composables
directory in preference of extracting reactive data/methods with the Composition API instead of with mixins. Or at least a composables
directory might be added to my standard file structure in addition to the mixins
directory.
plugins
The last directory I like to include for all my Vue projects is the plugins
directory. In a world of packages and libraries we sometimes end up doing more configuring and registering than we do actual coding. That's what this directory is for, including and setting up all the third party Vue stuff. While it's called plugins I don't always necessarily use the term in the strictest sense. In other words, it doesn't have be a third party lib registered via the Vue .use()
method. Often times it is, but other times it uses alternate methods of setting up the lib with Vue (such as .component()
). For libs that take a one or 2 line setup, I'll write it in a plugins/index.js
file. For those that take a more involved setup, I like to create a dedicated file for them in the plugins directory and then import it into the plugins/index.js
.
globals.js
This is the only standard file I really ever add. It's purpose is to add a limited number of global variables to the Vue app and, for SPAs that are client side only, usually the window.
This is the comment that usually adorns the top of this file.
/**
* Use this file to register any variables or functions that should be available globally
* ideally you should make it available via the window object
* as well as the Vue prototype for access throughout the app
* (register globals with care, only when it makes since to be accessible app wide)
*/
In Vue 2 this could be done like so:
Vue.prototype.$http = () => {}
In Vue 3 it looks like this:
const app = createApp({})
app.config.globalProperties.$http = () => {}
Though constantly warned of the dangers of globals, I read once that a "small smattering of global variables" is a very handy thing and it has proven useful for me ever since. This file makes it easy for me to know what those globals are and allows me not to have to think when wanting to add more.
While there are some community-wide standards that you would do well not to ignore, there are also a number of standards you can make for you or your team in order to make your code bases more predictable. While some of the standards mentioned above have proven useful for me there might be others that work well for you or your team. The kicker is sticking to them across projects so they will serve their purpose.
While standards for predictability are a great benefit for your large scale Vue.js applications, there's still more that can be done. Be sure to checkout the next article in this series where we dive into linting and formatting tools like ESLint and Prettier to keep your code clean, error free, and consistent.
We help companies all around the world build Vue applications more efficiently. Do you feel your team would benefit from professional Vue training or consulting? Or perhaps you need some extra hands developing your next milestone?
We are happy to hear your business needs and figure how we can help you out. Check out our business pricing for team training or contact us for business inquires any time!
Our goal is to be the number one source of Vue.js knowledge for all skill levels. We offer the knowledge of our industry leaders through awesome video courses for a ridiculously low price.
More than 200.000 users have already joined us. You are welcome too!
© All rights reserved. Made with ❤️ by BitterBrains, Inc.