If you’re building a modern Vue 3 application that’s more than just a weekend side project, it’s crucial to start with the right architecture. With over 15 years in full-stack development and hands-on experience scaling Vue apps for production, Iβve learned what works β and what causes chaos.
This guide breaks down the ideal folder structure, best practices, and toolchain for medium-sized Vue 3 apps using Vite, Pinia, Vue Router, and more. Whether you’re growing a startup MVP or migrating a legacy SPA, this will help you stay organized, maintainable, and future-ready.
π Why Choose Vue 3 + Vite?
Hereβs why I always recommend Vue 3 and Vite for scalable frontend projects:
- Seamless ecosystem integration: Pinia, Vue Router, VueUse, and beyond.
- Composition API with
<script setup>
keeps logic modular and clean. - Vite delivers ultra-fast dev builds, hot reloads, and zero-config.
π§± Recommended Folder Structure for Vue 3 Projects
ββ api/ # Axios config + domain API handlers
ββ composables/ # Reusable logic with Composition API
ββ constants/ # App-wide values and enums
ββ layouts/ # Layout wrappers (auth, public, etc.)
ββ router/ # Vue Router config
ββ services/ # Business logic and data abstraction
ββ store/ # Pinia stores
ββ views/ # Page-level components
ββ components/ # Shared components
ββ .env.* # Environment-specific config
ββ main.js # App entry point
ββ App.vue # Root component
This structure scales smoothly and helps onboard new devs quickly.
π Global State Management with Pinia
Pinia provides a clean, modular way to manage state. Business logic goes in services
, while state and actions live in store
files.
// store/productStore.js
import { defineStore } from 'pinia'
import { useProductService } from '@/services/productService'
export const useProductStore = defineStore('product', () => {
const productService = useProductService()
const products = ref([])
async function fetchProducts() {
products.value = await productService.getAll()
}
return {
products,
fetchProducts,
}
})
Select a reliable hosting provider that offers WordPress support or one-click installation. Most popular hosts (Bluehost, Hostinger, etc.) offer dedicated WordPress hosting plans.
π Centralized Axios Setup for APIs
Create a single Axios instance to handle headers, tokens, and error interception.
// api/index.js
import axios from 'axios'
const axiosInstance = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL,
})
axiosInstance.interceptors.request.use(config => {
const token = localStorage.getItem('token')
if (token) config.headers.Authorization = `Bearer ${token}`
return config
})
axiosInstance.interceptors.response.use(
response => response,
error => {
if (error.response?.status === 401) {
console.warn('Unauthorized access')
}
return Promise.reject(error)
}
)
export default axiosInstance
π§© Composables for Clean Reusability
Store logic like date formatting, debouncing, and screen size detection inside composables:
// composables/useDateTime.js
import dayjs from 'dayjs'
export function useDateTime() {
function formatDate(date, format = 'YYYY-MM-DD') {
return dayjs(date).format(format)
}
return { formatDate }
}
π§ Modular Vue Router with Navigation Guards
Group routes by module or auth type, and protect routes with guards:
// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import { useAuthStore } from '@/store/authStore'
const routes = [
{ path: '/', name: 'Home', component: () => import('@/views/HomeView.vue') },
{ path: '/login', name: 'Login', component: () => import('@/views/LoginView.vue') },
{
path: '/dashboard',
name: 'Dashboard',
component: () => import('@/views/DashboardView.vue'),
meta: { requiresAuth: true },
},
]
const router = createRouter({
history: createWebHistory(),
routes,
})
router.beforeEach((to, _, next) => {
const auth = useAuthStore()
if (to.meta.requiresAuth && !auth.isLoggedIn) next('/login')
else next()
})
export default router
π οΈ Use Constants and Environment Config
Centralized app settings improve consistency:
// constants/constants.js
export const APP_CONSTANTS = {
APP_NAME: 'AwesomeApp',
LANGUAGES: ['en', 'fr', 'de'],
}
Environment files keep dev/stage/prod config separate:
.env.local
.env.staging
.env.production
π¨ Styling, UI Libraries & Theming
- Vuetify = great for dashboards or admin panels.
- Tailwind CSS = powerful utility-first approach.
- Use CSS variables for theming (light/dark mode).
π Internationalization with Vue I18n
Support global users with vue-i18n
and lazy-loaded locales. Works great even at medium scale.
π Notifications & Global UI
Build a global snackbar/toast system using a Pinia store + service layer:
notificationService.success('Saved successfully')
notificationService.error('Something went wrong')
π§ Bonus: VueUse Utilities
Use VueUse to simplify logic:
useLocalStorage()
to sync with PiniauseBreakpoints()
to build adaptive layouts
β Final Thoughts
Medium-sized Vue apps donβt have to be messy. With the right structure, you can scale confidently, onboard new devs fast, and keep your codebase maintainable for years.
Use this guide as a foundation β then iterate based on your project needs.
Need help planning your Vue architecture or automating your workflow? Letβs talk!