Organize Your Project for Scalability
A messy folder structure leads to messy code. Clean code begins with a clean project layout.
Recommended folder structure:
src/
assets/
components/
composables/
constants/
services/
stores/
utils/
views/
router/
App.vue
main.js
| Folder / File | Purpose |
|---|---|
src/ |
Root directory containing all frontend source code. |
assets/ |
Static assets such as images, fonts, icons, global styles (CSS/SCSS). |
components/ |
Reusable shared UI components used across the project. |
composables/ |
Reusable logic using Vue Composition API (e.g., useFetch, useAuth). |
constants/ |
Centralized enums, configuration values, status codes. |
services/ |
API services, HTTP logic, business service abstraction. |
stores/ |
Global state management using Pinia. |
utils/ |
Helper functions, validators, formatters, pure functions. |
views/ |
Page-level components rendered by Vue Router. |
router/ |
Vue Router configuration, routing tables, navigation guards. |
App.vue |
Root Vue component that hosts the entire application layout. |
main.js |
Application entry point: initializes app, mounts router & store. |
This separation prevents bloated components and encourages reuse.
Use Consistent Naming Conventions
Naming is one of the most important aspects of clean code. So, we need to follow the naming rules:
Component naming: should use PascalCase
Example: UserProfileCard.vue, LoginForm.vue, SidebarMenu.vue
File naming for composables: should use camelCase
Example: useUser.js, useAuth.js, usePagination.js
Method naming: should describe intention:
Example:
❌ Not good
doPrice()
check()
getData()
✅️ Good
calculateTotalPrice()
validateForm()
fetchUserProfile()
Avoid abbreviations
Example:
❌ Not good
cfg, usr, prd
✅️ Good
config, user, product
Keep Components Small and Focused (Single Responsibility Principle)
A clean Vue component should contain:
✅️ The template
✅️ Local UI logic
✅️ Minor state transitions
It should not contain:
❌ Data fetching logic
❌ Business rules
❌ Repetitive utilities
❌ Large computed logic
❌ Large watchers
When a component grows >300 lines, we should refactor it.
Use Composables to Extract Reusable Logic
Example: Before (bloated component)
<script setup>
import { ref, onMounted } from 'vue'
const users = ref([])
const loading = ref(false)
async function loadUsers() {
loading.value = true
const res = await fetch('/api/users')
users.value = await res.json()
loading.value = false
}
onMounted(() => loadUsers())
</script>
Problems:
❌ Hard to reuse
❌ Hard to test
❌ Component becomes large
After: using composable to clean code
useUsers.js
import { ref, onMounted } from 'vue'
export function useUsers() {
const users = ref([])
const loading = ref(false)
async function loadUsers() {
loading.value = true
const response = await fetch('/api/users')
users.value = await response.json()
loading.value = false
}
onMounted(loadUsers)
return { users, loading, loadUsers }
}
UserList.vue
<script setup>
import { useUsers } from '@/composables/useUsers'
const { users, loading } = useUsers()
</script>
Benefits:
✅️ Reusable in multiple components
✅️ Testable
✅️ Cleaner component
Keep Templates Clean and Expressive
Templates should read almost like HTML.
❌ Bad Template
<p>{{ price - discount > 0 ? price - discount : 0 }}</p>
✅️ Clean Template (Move logic to computed)
const finalPrice = computed(() => Math.max(price.value - discount.value, 0))
<p>{{ finalPrice }}</p>
Diagram: Template Logic Separation
Template → simple expressions
Script → complex logic
Composable → business logic
Use Centralized State Management Wisely (Pinia Recommended)
State should not float around components in unpredictable ways.
Pinia is the recommended state solution.
✅️ Example Pinia Store
import { defineStore } from 'pinia'
export const useCartStore = defineStore('cart', {
state: () => ({
items: []
}),
actions: {
addItem(item) {
this.items.push(item)
},
removeItem(id) {
this.items = this.items.filter(i => i.id !== id)
}
}
})
This keeps components lightweight.
Avoid Magic Strings and Use Constants
Magic strings make code fragile.
❌ Bad
if (status === "SUCCESS_001") {
...
}
✅️ Good
status.js
export const STATUS = {
SUCCESS: 'SUCCESS_001',
FAILED: 'FAILED_999'
}
Usage:
if (status === STATUS.SUCCESS) {...}
Add Proper Documentation and Comments (When Needed)
Do not write comments that explain the obvious.
Document intention, not implementation.
✅️ Good JSDoc:
/**
* Formats an ISO date into DD/MM/YYYY format.
*/
function formatDate(dateStr) { ... }
Add Unit Tests to Reinforce Clean Architecture
Testing ensures your clean code stays clean as the project evolves.
Recommended tools:
Vitest
Vue Test Utils
Conclusion
Clean code in Vue.js is not about perfection—it’s about clarity, intention, and long-term maintainability.
By applying the principles covered in this guide:
- Organizing your project
- Naming consistently
- Keeping components small
- Using composables
- Keeping templates clean
- Centralizing shared state
- Using testing tools
Your Vue.js applications will become more maintainable, scalable, and enjoyable for everyone on the team.
[References]
Image source: https://www.freepik.com/free-photo/turned-gray-laptop-computer_12661377.htm
Ready to get started?
Contact IVC for a free consultation and discover how we can help your business grow online.
Contact IVC for a Free Consultation


