Structuring Vue Components

Written by Alex Jover Morales

As soon as you start learning Vue you find out that the atomic unit of its architecture are components. In fact, that's nothing Vue-specific: any component-based technology, such as React or Angular, work the same way.

Then you learn more about components and how to build them. You start creating your own components, combining them and applying different communication patterns in order to build more complex components. Sure you're doing great and keep the components as small as possible so you can reuse some of them in several places of your application.

But at some point, the application grows. You have many components and things are starting to get messy: it's hard to find the right component and to know its responsibility, then maintainability starts to suffer.

No worries, in this article we're going to see different ways and tips you can use to structure your application components.

First Steps: Pages

A basic structure commonly seen when starting an application from scratch has a components folder, along with a main.js entry file and an App.vue component:

src/
        main.js
        App.vue
        components/

After a while, you'll have several components. A very simple example of that structure, could be:

components/
        ArticlePage.vue
        ArticleTitle.vue
        ArticleList.vue
        AppButton.vue
        AppFooter.vue
        AppHeader.vue
        LastArticlesSection.vue
        AppList.vue
        UserPage.vue

Note: Vue’s style guide multi-word rule tells us not to use a single word to name a component, that’s why we’re prefixing some with “App”

In component-based technologies, everything is a component. Having a fact like that, we must think about a way to categorize them.

If you take a look at above's structure, you can easily identify one type of components: pages. Thus, we can create a pages folder when we can place our page components:

components/
        ArticleTitle.vue
        ArticleList.vue
        AppButton.vue
        AppFooter.vue
        AppHeader.vue
        LastArticlesSection.vue
        AppList.vue
pages/
        ArticlePage.vue
        UserPage.vue

That's an easy split and already gives us a bit of structure, so that when we know where to place and search for pages components.

However, a page is usually composed by more components or sections. Related to the ArticlePage, we see the ArticleTitle, ArticleList and LastArticlesSection components.

Let's assume ArticleTitle and LastArticlesSection are meant to exist only in the ArticlePage, but ArticleList is a component that fetches and renders a list of articles, and it can be used in multiple places.

Master Vue.js with Vue School

Top notch Vue.js courses and over 200 lessons for just $12 per month!

Given these facts, it would make sense that ArticleTitle and LastArticlesSection are placed along with its page in a common folder:

components/
        ArticleList.vue
        AppButton.vue
        AppFooter.vue
        AppHeader.vue
        AppList.vue
pages/
        ArticlePage/
                index.vue
                ArticleTitle.vue
                LastArticlesSection.vue
        UserPage.vue

We've also renamed ArticlePage.vue to index.vue. Thanks to how module resolution works, when we import the article page in this way:

import ArticlePage from "@/pages/ArticlePage"

It will be imported it from pages/ArticlePage.vue or pages/ArticlePage/index.vue. For that reason, we can always start creating the pages as a component and later move it to a folder without having to change the way to import it, making refactoring easier.

Common components

We still have components with mixed responsibilities and domains in the components folder.

There is another split point on UI components: those that are reusable across the whole app. They communicate just by using props and events, not holding any application logic.

In the structure of this example, we see that the AppButton and AppList components are one of them, so let's place them under the ui folder:

components/
        ui/
                AppButton.vue
                AppList.vue
        ArticleList.vue
        AppFooter.vue
        AppHeader.vue
pages/
        ArticlePage/
                index.vue
                ArticleTitle.vue
                LastArticlesSection.vue
        UserPage.vue

The AppFooter and AppHeader components, however, are not exactly UI components. Instead they're more like layout components since the app will have only one footer and header. You don't need to, but you can move them to a layout folder if you want:

components/
        layout/
                AppFooter.vue
                AppHeader.vue                
        ui/
                AppButton.vue
                AppList.vue
        ArticleList.vue
pages/
        ArticlePage/
                index.vue
                ArticleTitle.vue
                LastArticlesSection.vue
        UserPage.vue

What about the ArticleList component? We can have components that are reusable in different pages, so we shouldn't place them with the page components. However, they belong to a specific domain, in this case to the article domain.

Let's call them domain components. A good way to organize them is to place them in separate folders under components, one folder per domain:

components/
        article/
                AppList.vue
        layout/
                AppFooter.vue
                AppHeader.vue                
        ui/
                AppButton.vue
                AppList.vue
pages/
        ArticlePAge/
                index.vue
                ArticleTitle.vue
                LastArticlesSection.vue
        UserPage.vue

Again, we can remove the Article prefix since the path is already representative. In fact, now we have two List components:

import ArticleList from "@/components/article/List"
import AppList from "@/components/ui/AppList"

Lastly, what about the rest of the components? There can be components that belong to no domain, are not ui or layout. They can be some kind of utility components that have some logic but delegate the rendering to children components.

For example, imagine an Observer component that detects when its children intersect on the screen. Another example is vue-no-ssr: a component that renders its children only when it's running on the client, in case you're applying Server-Side Rendering (SSR) or you're using Nuxt.

We can place this kind of uncategorised common components under a common folder:

components/
        article/
                AppList.vue
        common/
                AppObserver.vue
                NoSSR.vue
        layout/
                AppFooter.vue
                AppHeader.vue                
        ui/
                AppButton.vue
                AppList.vue
pages/
        ArticlePage/
                index.vue
                ArticleTitle.vue
                LastArticlesSection.vue
        UserPage.vue

Wrapping Up

We've seen different techniques to structure your application components. They might be helpful for you or not, but I hope at least you got some ideas that you can use from now on.

Master Vue.js with Vue School

Top notch Vue.js courses and over 200 lessons for just $12 per month!

  1. That’s generally a better structure than what my apps have evolved into. One thing I do that I think works well though is the use of a “components/super_components” folder. My distinction between a regular component and a super component is a super component is one which makes its own ajax calls to get state instead of merely pulling in props. With that distinction I can quickly see which components will work well all on their own (just by passing props into them), and which ones are dependent on the server API (and therefore operate on their own).

    I’m not sure if this is what’s considered best practice, but it works well for me.

    1. I always use an own made ajax method wrapped as a promise, but abstracted in a function like this ajax(url, type, data); Where url = url/route, type = get,post,put,delete, data = data u want to post and/or the callback data from the server like in a ‘get’ request.
      Then I just have to do this;

      ajax(‘https://mydomain.org/someapi’, ‘GET’, someDataFromApi)
      .then(someDataFromApi => // do something with data)
      .then(// do more things with data)

      Very composable, modular and fully decoupled

      1. Yeah my ajax calls are abstracted into getters and setters as well, but my distinction between regular components and super components is that regular components will only get data though props, and output through $emit.

        Super components may also use the ajax getter/setter methods. That way when you’re looking at a super component you know that you’re not dealing with a pure functional component and there will be side effects.

  2. This is a good recommendation. I use views instead of pages. I also do not typically use any sub-folders inside my views or components directories, which makes it easier for type hinting and import statements. I just use the style guide suggestions for naming.

  3. This is an important topic, as I’m sure many Vue developers hit this point where they need more organization. Here’s how I would have done the pages directory, using show as the component for showing an Article or User. I also suspect this would make autocomplete a lot easier when I go searching for a component to use. Then again, ArticleTitle and ArticleLast aren’t really “Pages” so maybe perhaps don’t belong in this directory. Thoughts? https://uploads.disquscdn.com/images/797206b2f227265a82cc327f6a46bdfe153f0046719ae5f7cd65fbecb1e9778b.jpg

  4. The module resolution you used for ArticlePage/index.vue doesn’t work in my application, I see that the import for index.js in a folder works, but not for .vue file (component), does it require any changes to the webpack config in order to enable this?

  5. Thanks for the recommendation!

    it seems that ‘components/article/AppList.vue’ is supposed to be ‘components/article/List.vue’ at the end by the article, right?

  6. I asked myself this exact same question last week and came up with a similar approach but more Domain oriented. I took into account that most domains comme with vuex modules, have several routes and sometimes assets. Here is what I did:

    articles/
    pages/
    ArticleZoom.vue
    ArticleList.vue
    components/
    ArticleTitle.vue
    LastArticleSection.vue
    stateManagement/
    ArticleZoom.js
    ArticleList.js
    assets/…
    article-schema.js (normalizr)
    article-routes.js (vue-router)
    layout/
    AppFooter.vue
    AppHeader.vue

    component-library/
    all the agnostic components (that can easily be externalized to a different library to be used in other projects)
    services/
    ApiService.js
    AuthenticationService.js
    commonAssets/
    router.js
    store.js
    App.vue
    main.js

    I’m not sure it’s perfect yet, but certainly better than what I had before.

    Thanks for the great article !

Leave a Reply

Your email address will not be published. Required fields are marked *

Up Next:

Reusing Logic in Vue Components

Reusing Logic in Vue Components