DEV Community

Jesus Guerrero
Jesus Guerrero

Posted on • Edited on

App structure for big Vue projects

Choosing the right folder structure for a middle-size or big vue application might be intimidating and it's even more so when there are not many suggestions about the topic.

From 2017 to the moment of writing this post, I've been working on Vue applications for years from medium to large, established apps migrating to Vue to projects at the start of the process. From Nuxt, Laravel, and Vue SPA; here I will try my best to describe what I think has been the more intuitive easy to work and maintain folder structure.

The alternatives

First I want to talk about the alternatives considered.

Atomic Design

This is a very complete structure to define your building blocks according to their complexity, size, and how they interact with each other taking inspiration from one of the most well-structured systems the building blocks that compose our body

It is organized as follows:

  • Atoms
  • Molecules
  • Organisms

Those will help us to aisle functionality, making portable and reusable components even easier to unit test.

Then we used to group those organisms in templates to give them context and form in a layout and pages that are the single entry point of the content we show to our users.

  • Atoms
  • Molecules
  • Organisms
  • Templates
  • Pages
|_atoms/
   |__Input.vue
   |__Button.vue
   |__Label.vue
|_molecules
   |__FieldGroup.vue
|__organisms
   |__ContactForm
Enter fullscreen mode Exit fullscreen mode

Benefits

  • Hint of complexity
    You get a hint about the complexity of your components ahead. just by seeing where the component is placed: atoms are an indicator that the complexity is low and if are placed in organisms it is more complex.

  • Reusability
    As you are working with building blocks they are like Legos that you can move around atoms can be part of many molecules and organisms

  • Testability
    As components tend to be smaller and only do in isolation

  • Perfect match with design systems
    If you are starting an application from 0 and need to build a consistent design system this is almost a no-brainer

Cons

  • When applied directly in an application structure you'll feel your components are all over the place.
  • If an atom is just used by one molecule or single organism you can't group them by context
  • Business logic

In my experience, we can get the most from Atomic Design when we are building a component library/Design System that will be the foundation of our system: Buttons, Input, Input groups, Form Wizards, SearchBars, Selectors, etc.

The Chosen One

When you are building an application you need as much context as possible in your project in the components that handle the business logic. For example, if I have a budget

|_Budget/
   |__Budget.vue
   |__BudgetCategory.vue
   |__BudgetCategoryItem.vue
Enter fullscreen mode Exit fullscreen mode

The dev experience could improved greatly when building a new feature or removing or refactoring a component because it has much more context of where is used and its props (BudgetCategoryItem could have a BudgetCategory prop for example, and not a generic row prop) en what domain dominio y and which is its closest parent component.

Proposed structure:

|__ assets
|__ components/
|__ locales/
|__ plugins
    |__ i18n/index.ts
    |__ auth0/index.ts
    |__ loger/index.ts
    |__ axios/index.ts
|__ config/
|   |__ index.ts
|__ domains/
    |__ [domain]
       |__ api/  
       |__ models/
       |__ enums/
       |__ components/
           |__ RequestModal.vue
       |__ composables/
       |__ tests/unitTest.ts 
|__ pages
|   |__ auth/
|       |__ Partials/
|           |__ AuthLayout.vue     
|       |__ AuthSignIn.vue
|       |__ AuthSignUp.vue
|       |__ AuthRecover.vue
|       |__ AuthReset.vue
|   |__ [domain]
|        |__ Partials/
|            |__ SubpageLayout.vue
|            |__ [ComponentUsedOnce].vue
|        |__ subpageList.vue
|        |__ subpageEdit.vue
|        |__ subpagesCreate.vue
|__ router/index.ts     
|__ store/
     |__ Modules/
     |__ index.ts 
|__ utils/   
|__ App.vue
|__ main.ts

Enter fullscreen mode Exit fullscreen mode

assets

This directory can contain images, SVG, CSS or SCSS for the application.

components

This component holds all the shared components of the application and they can be used in any part of the system.

config

Access to env variables with import.env.VITE_VARIABLE_NAME across our app loads too much dependency on the bundler to centralize all this in a single place and give us some good ts support and add default values.

In one of my projects, it looks like:

``// config/index.ts 
interface AppConfig {
    FIREBASE_API_KEY: string;
    FIREBASE_PROJECT_ID: string;
    FIREBASE_APP_ID: string;
    FIREBASE_SENDER_ID: string;
    PUSH_PK: string;
    MEASUREMENT_ID: string;
    GOOGLE_APP_KEY: string;
    GOOGLE_APP_CLIENT: string;
    FIREBASE_VAPID_KEY: string;
    IS_DEMO: boolean;
}

const isDemo = import.meta.env?.VITE_APP_DEMO

export const config: AppConfig = {
    FIREBASE_API_KEY: import.meta.env.VITE_FIREBASE_APP_KEY,
    FIREBASE_PROJECT_ID: import.meta.env.VITE_FIREBASE_PROJECT_ID,
    FIREBASE_APP_ID: import.meta.env.VITE_FIREBASE_APP_ID,
    FIREBASE_SENDER_ID: import.meta.env.VITE_FIREBASE_SENDER_ID,
    PUSH_PK: import.meta.env.VITE_PUSH_PK,
    MEASUREMENT_ID: import.meta.env.VITE_MEASUREMENT_ID,
    GOOGLE_APP_KEY: import.meta.env.VITE_GOOGLE_APP_KEY,
    GOOGLE_APP_CLIENT: import.meta.env.VITE_GOOGLE_CLIENT_ID,
    FIREBASE_VAPID_KEY: import.meta.env.VITE_FIREBASE_VAPID_KEY,
    IS_DEMO: Boolean(isDemo) && isDemo !== 'false'
}
Enter fullscreen mode Exit fullscreen mode

domains

Contains the business logic of the application grouped by domain (remember DDD?) and each domain will have the sections: api, models, components, composables, unit tests

Subfolders:

  • api: contains all the API calls of the application as endpoints are in a central place we change once if it used in different parts of the app

  • models: Contains all the interfaces and types of the domain

  • components: Contains all the domain-related components

  • composables: Our domain-related vue composables

  • tests: Our unit tests

Pages

These components would be used only by the router and would make the main call to the endpoint, permissions (ACL), and also group different domain controllers or partials.

Subfolders

  • Partials: Partials are the only directory allowed on pages. They are only used by their respective pages BudgetSectionTemplate.vue think about them as templates or layouts.

Plugins

Third-party services that you can change like eg. Auth0, Axios, i18n

Trade-offs

Pros:

  • Provide context about business logic/business domain in the application. It would benefit onboarding new devs to the code.

  • As the code is grouped by domains you can extract functionality easily to make a library if it's required for another project.

Cons

  • Nested folders

  • Elements could have more than one domain

Solving conflict points:

  1. How to avoid over-nested components? Sometimes we tend to group subcomponents by their technical connotation eg.
Components 
   |__ Notifications 
      |__ NotificationCard 
      |__ NotificationTypes/ 
         |__ TaskApproved 
         |__ TaskRejected
Enter fullscreen mode Exit fullscreen mode

An unnecessary nesting could be avoided by naming the component with the domain in front or creating another domain.

Eg.1

Components 
   |__ Notifications 
   |__ NotificationCard 
   |__ NotificationTypeTaskApproved 
   |__ NotificationTypeTaskRejected
Enter fullscreen mode Exit fullscreen mode

Eg. 2

Components 
   |__ Notifications 
      |__ NotificationCard 
      |__ // ...more components 
   |__ NotificationTypes 
      |_ NotificationTypeTaskApproved 
      |_ NotificationTypeTaskRejected
Enter fullscreen mode Exit fullscreen mode
  1. How to deal with a component that seems to have multiple domains?

    Sometimes a domain may cross with another for example widgets that are shown in the dashboard, should be put in a dashboard domain or its domain (Projects/ Tasks/ Tracks).

    In this case we should analize the composition of the component and the requirements In this case the widgets are only used in the dashboard and if we go to its composition and props like title graphTypeand data all are the same in structure. Then we can conclude that each widget can be safely placed in the Dashboard domain.

Top comments (4)

Collapse
 
dimau profile image
Dmitrii Ushakov

What about Feature Sliced Design? It looks similar

Collapse
 
jesusantguerrero profile image
Jesus Guerrero

It's a good alternative, it certainly shares some similarities but I think FSD is a more nested option.

For example a Budget widget or feature would go in independent folders instead of being in the same Domain.

Collapse
 
drfcozapata profile image
Francisco Zapata

GREAT!!!

Collapse
 
jesusantguerrero profile image
Jesus Guerrero

Thank you!