Vue 3 Project Structure & Component Design Standards · VUE-01
Project Structure
Use TypeScript for Type Safety · VUE-01.1 · MUST
Typescript enhances code reliability, improves developer experience and catches errors at compile-time.
Apply a Consistent and Organised Vue Project Structure · VUE-01.2 · MUST
To aide maintainability and scalability, a consistent project structure must be used.
The following is a default Vue project structure:
src/: Contains all source files.components/: Reusable components.views/: Components linked to routes.assets/: Static assets (images, fonts, etc.).store/: Pinia stores (if using Pinia for state management)/router/: Routing configuration.
When using a framework which has a different project structure, then that project structure must be applied instead. See NUXT-01.1 Nuxt Project Structure Standard.
Follow a Consistent Project Structure · VUE-01.3 · MUST
Encourage maintainable order and predictability in the codebase, making it easier to navigate and manage.
Apply Consistent Error Handling Practices · VUE-01.4 · MUST
Ensure a uniform approach to error handling, making it easier to debug and maintain.
Optimize Performance with Lazy Loading · VUE-01.5 · MUST
Enhance user experience by optimising initial load times through reduced unnecessary computations.
Lazy Loading Example (index.vue)
const routes = [
{
path: '/home',
name: 'Home',
component: () => import('../views/Home.vue'), // Lazy-loaded component
},
{
path: '/about',
name: 'About',
component: () => import('../views/About.vue'), // Lazy-loaded component
},
];Optimise Performance with Debouncing · VUE-01.6 · MUST
Enhance user experience by optimising how frequently a function is called when responding to user input.
Debouncing ensures that a function is not called too frequently in response to user input, such as typing in a search box. This helps improve performance by limiting the rate at which expensive operations (like API calls) are triggered.
Debouncing Example
<template>
<input v-model="query" @input="debouncedSearch" placeholder="Search..." />
</template>
<script setup>
import { ref, watch } from 'vue';
import debounce from 'lodash';
const query = ref('');
const search = (term) => {
// Perform search or API call
expensiveSearchOperation(term);
};
// Debounce the search function to limit the rate of API calls
const debouncedSearch = debounce(() => search(query.value), 300);
</script>Be Cautious When Using External Dependencies · VUE-01.7 · MUST
While external libraries can speed up development, they can introduce risks in the forms of: security vulnerabilities, bugs, performance costs, inconsistent UI designs and maintenance issues.
Dependencies should never be added if they offer simple functionality which can be implemented with reasonable effort.
This minimizes the risk of being blocked by external bugs, ensures better control over the code, and reduces reliance on third-party updates and maintenance.
Use Provide/Inject API for Dependency Injection · VUE-01.8 · SHOULD
This provides efficiently managed dependencies and state across component hierarchies without prop drilling.
Component Design
Leverage Script Setup Syntax for Single-File Components · VUE-01.9 · MUST
By utilising
Use a Consistent Naming Convention · VUE-01.11 · MUST
Improve codebase readibility and consistency, making it easier for teams to work together.
Use Scoped Styles for Component CSS · VUE-01.12 · MUST
Ensure that styles are encapsulated and do not leak into other components.
Utilize Async Components for Code Splitting · VUE-01.13 · MUST
Optimise performance by lazy-loading components and reducing initial bundle size.
Use Composition API for Logic and State Management · VUE-01.14 · MUST
The Composition API offers code reusability, maintainability and readibility by encouraging structuring of more organised components. By confining each part of logic we also enhance our scalability.
This is considered a better practice than the options API, which can lead to logic being scattered across different lifecycle hooks and options.
Prefer Template Refs Over IDs for DOM Manipulation · VUE-01.15 · MUST
Provide a more Vue-centric and reactive way to access and manipulate DOM elements.
Template Ref (✅)
<template>
<input ref="usernameInput" type="text" placeholder="Enter username" />
</template>
<script="setup">
import { ref } from 'vue';
const usernameInput = ref(null);
const focusInput = () => {
if (usernameInput.value) {
usernameInput.value.focus(); // Directly accessing the input element
}
};
</script>
Template Id (❌)
<template>
<input id="usernameInput" type="text" placeholder="Enter username" />
</template>
<script setup>
const focusInput = () => {
// Bad Practice
const inputElement = document.getElementById('usernameInput');
if (inputElement) {
inputElement.focus(); // Accessing the input element via ID
}
};
</script>
Handle Side Effects with Watchers and Lifecycle Hooks · VUE-01.16 · MUST
Clearly separate side effects from main component logic, improving maintainability and testability.
Watchers in Vue are used to perform actions in response to changes in reactive data properties. If you have a ref or reactive in your component of which has a value you watch to react to.
Example
watch(searchQuery, (newQuery) => {
if (newQuery) {
fetchResults(newQuery);
}
});
Use computed for Derived State · VUE-01.17 · MUST
Leverage Vue’s computed properties to optimize performance and improve code maintainably.
Performance
By assigning composables to const, we also leverage caching to avoid unecessary computations.
<template>
<div>
<p>Full Name: {{ fullName }}</p>
</div>
</template>
<script setup>
import { ref, computed } from 'vue';
const firstName = ref('John');
const lastName = ref('Doe');
// Any update to firstName or lastName will cause the value of fullName to change.
const fullName = computed(() => <code>${firstName.value} ${lastName.value}</code>);
</script>
Avoid Complex Logic in Templates · VUE-01.10 · SHOULD
Keep templates clean and declarative; complex logic should be moved to the setup function or composables.
If you have complex logic in a template, the template is doing too much and is likely an indicator to split login into composables or helpers.