Vue.js Development Guide 2026: Progressive Frontend Framework

Vue.js Development Guide 2026: Progressive Frontend Framework
Vue.js has established itself as the progressive JavaScript framework, offering an approachable learning curve while providing powerful features for complex applications. With Vue 3's Composition API and improved performance, it's become the framework of choice for developers seeking flexibility and productivity in 2026.
Why Vue.js Excels in Modern Development
Progressive Framework Design
Vue.js can be adopted incrementally, from enhancing existing pages to building full-scale single-page applications. This flexibility makes it ideal for both startups and enterprise migrations.
Developer Experience
Vue's intuitive template syntax, excellent documentation, and comprehensive tooling create an exceptional developer experience. The Vue DevTools and Vite integration provide fast development cycles.
Performance and Bundle Size
Vue 3's optimized reactivity system and tree-shaking capabilities result in smaller bundle sizes and better runtime performance compared to other frameworks.
Vue 3 Composition API Fundamentals
Reactive Data and Computed Properties
import { ref, reactive, computed, watch } from 'vue'
export default {
setup() {
// Reactive references
const count = ref(0)
const user = reactive({
name: 'John Doe',
email: 'john@example.com',
profile: {
avatar: '',
bio: ''
}
})
// Computed properties
const doubleCount = computed(() => count.value * 2)
const userDisplayName = computed(() =>
user.name || user.email.split('@')[0]
)
// Watchers
watch(count, (newValue, oldValue) => {
console.log(`Count changed from ${oldValue} to ${newValue}`)
})
watch(() => user.email, (newEmail) => {
console.log('Email updated:', newEmail)
}, { immediate: true })
// Methods
const increment = () => {
count.value++
}
const updateUser = (userData) => {
Object.assign(user, userData)
}
return {
count,
user,
doubleCount,
userDisplayName,
increment,
updateUser
}
}
}
Lifecycle Hooks
import { onMounted, onUpdated, onUnmounted, ref } from 'vue'
export default {
setup() {
const data = ref([])
const loading = ref(false)
onMounted(async () => {
console.log('Component mounted')
await fetchData()
})
onUpdated(() => {
console.log('Component updated')
})
onUnmounted(() => {
console.log('Component unmounted')
// Cleanup subscriptions, timers, etc.
})
const fetchData = async () => {
loading.value = true
try {
const response = await fetch('/api/data')
data.value = await response.json()
} catch (error) {
console.error('Failed to fetch data:', error)
} finally {
loading.value = false
}
}
return {
data,
loading,
fetchData
}
}
}
Component Architecture
Single File Components
<template>
<div class="user-card" :class="{ active: isActive }">
<img :src="user.avatar" :alt="user.name" class="avatar">
<div class="user-info">
<h3>{{ user.name }}</h3>
<p>{{ user.email }}</p>
<span class="status" :class="statusClass">{{ user.status }}</span>
</div>
<div class="actions">
<button @click="$emit('edit', user)" class="btn-primary">
Edit
</button>
<button @click="handleDelete" class="btn-danger">
Delete
</button>
</div>
</div>
</template>
<script>
import { computed } from 'vue'
export default {
name: 'UserCard',
props: {
user: {
type: Object,
required: true
},
isActive: {
type: Boolean,
default: false
}
},
emits: ['edit', 'delete'],
setup(props, { emit }) {
const statusClass = computed(() => ({
'status-online': props.user.status === 'online',
'status-offline': props.user.status === 'offline',
'status-away': props.user.status === 'away'
}))
const handleDelete = () => {
if (confirm(`Delete user ${props.user.name}?`)) {
emit('delete', props.user.id)
}
}
return {
statusClass,
handleDelete
}
}
}
</script>
<style scoped>
.user-card {
display: flex;
align-items: center;
padding: 1rem;
border: 1px solid #e0e0e0;
border-radius: 8px;
margin-bottom: 0.5rem;
transition: all 0.3s ease;
}
.user-card:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.user-card.active {
border-color: #007bff;
background-color: #f8f9fa;
}
.avatar {
width: 50px;
height: 50px;
border-radius: 50%;
margin-right: 1rem;
}
.user-info {
flex: 1;
}
.status-online { color: #28a745; }
.status-offline { color: #6c757d; }
.status-away { color: #ffc107; }
</style>
Composables (Custom Hooks)
// composables/useUsers.js
import { ref, computed } from 'vue'
export function useUsers() {
const users = ref([])
const loading = ref(false)
const error = ref(null)
const activeUsers = computed(() =>
users.value.filter(user => user.status === 'online')
)
const fetchUsers = async () => {
loading.value = true
error.value = null
try {
const response = await fetch('/api/users')
if (!response.ok) throw new Error('Failed to fetch users')
users.value = await response.json()
} catch (err) {
error.value = err.message
} finally {
loading.value = false
}
}
const createUser = async (userData) => {
try {
const response = await fetch('/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(userData)
})
if (!response.ok) throw new Error('Failed to create user')
const newUser = await response.json()
users.value.push(newUser)
return newUser
} catch (err) {
error.value = err.message
throw err
}
}
const updateUser = async (userId, userData) => {
try {
const response = await fetch(`/api/users/${userId}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(userData)
})
if (!response.ok) throw new Error('Failed to update user')
const updatedUser = await response.json()
const index = users.value.findIndex(u => u.id === userId)
if (index !== -1) {
users.value[index] = updatedUser
}
return updatedUser
} catch (err) {
error.value = err.message
throw err
}
}
const deleteUser = async (userId) => {
try {
const response = await fetch(`/api/users/${userId}`, {
method: 'DELETE'
})
if (!response.ok) throw new Error('Failed to delete user')
users.value = users.value.filter(u => u.id !== userId)
} catch (err) {
error.value = err.message
throw err
}
}
return {
users,
activeUsers,
loading,
error,
fetchUsers,
createUser,
updateUser,
deleteUser
}
}
// Usage in component
import { useUsers } from '@/composables/useUsers'
export default {
setup() {
const {
users,
activeUsers,
loading,
error,
fetchUsers,
createUser,
deleteUser
} = useUsers()
onMounted(() => {
fetchUsers()
})
return {
users,
activeUsers,
loading,
error,
createUser,
deleteUser
}
}
}
State Management with Pinia
Store Definition
// stores/user.js
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const useUserStore = defineStore('user', () => {
// State
const users = ref([])
const currentUser = ref(null)
const loading = ref(false)
const error = ref(null)
// Getters
const activeUsers = computed(() =>
users.value.filter(user => user.status === 'online')
)
const userCount = computed(() => users.value.length)
const getUserById = computed(() => {
return (id) => users.value.find(user => user.id === id)
})
// Actions
const fetchUsers = async () => {
loading.value = true
error.value = null
try {
const response = await fetch('/api/users')
users.value = await response.json()
} catch (err) {
error.value = err.message
} finally {
loading.value = false
}
}
const addUser = async (userData) => {
try {
const response = await fetch('/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(userData)
})
const newUser = await response.json()
users.value.push(newUser)
return newUser
} catch (err) {
error.value = err.message
throw err
}
}
const updateUser = async (userId, userData) => {
try {
const response = await fetch(`/api/users/${userId}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(userData)
})
const updatedUser = await response.json()
const index = users.value.findIndex(u => u.id === userId)
if (index !== -1) {
users.value[index] = updatedUser
}
if (currentUser.value?.id === userId) {
currentUser.value = updatedUser
}
return updatedUser
} catch (err) {
error.value = err.message
throw err
}
}
const removeUser = async (userId) => {
try {
await fetch(`/api/users/${userId}`, { method: 'DELETE' })
users.value = users.value.filter(u => u.id !== userId)
if (currentUser.value?.id === userId) {
currentUser.value = null
}
} catch (err) {
error.value = err.message
throw err
}
}
const setCurrentUser = (user) => {
currentUser.value = user
}
return {
// State
users,
currentUser,
loading,
error,
// Getters
activeUsers,
userCount,
getUserById,
// Actions
fetchUsers,
addUser,
updateUser,
removeUser,
setCurrentUser
}
})
Using Store in Components
<template>
<div class="user-management">
<div v-if="loading" class="loading">Loading users...</div>
<div v-else-if="error" class="error">{{ error }}</div>
<div v-else>
<div class="stats">
<p>Total Users: {{ userCount }}</p>
<p>Active Users: {{ activeUsers.length }}</p>
</div>
<UserCard
v-for="user in users"
:key="user.id"
:user="user"
:is-active="currentUser?.id === user.id"
@edit="handleEditUser"
@delete="handleDeleteUser"
/>
</div>
</div>
</template>
<script>
import { useUserStore } from '@/stores/user'
import UserCard from '@/components/UserCard.vue'
export default {
components: {
UserCard
},
setup() {
const userStore = useUserStore()
const handleEditUser = (user) => {
userStore.setCurrentUser(user)
// Navigate to edit form
}
const handleDeleteUser = async (userId) => {
try {
await userStore.removeUser(userId)
} catch (error) {
console.error('Failed to delete user:', error)
}
}
// Fetch users on component mount
onMounted(() => {
userStore.fetchUsers()
})
return {
...storeToRefs(userStore),
handleEditUser,
handleDeleteUser
}
}
}
</script>
Vue Router and Navigation
Router Configuration
// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import { useAuthStore } from '@/stores/auth'
const routes = [
{
path: '/',
name: 'Home',
component: () => import('@/views/Home.vue')
},
{
path: '/users',
name: 'Users',
component: () => import('@/views/Users.vue'),
meta: { requiresAuth: true }
},
{
path: '/users/:id',
name: 'UserDetail',
component: () => import('@/views/UserDetail.vue'),
props: true,
meta: { requiresAuth: true }
},
{
path: '/login',
name: 'Login',
component: () => import('@/views/Login.vue'),
meta: { guest: true }
}
]
const router = createRouter({
history: createWebHistory(),
routes
})
// Navigation guards
router.beforeEach((to, from, next) => {
const authStore = useAuthStore()
if (to.meta.requiresAuth && !authStore.isAuthenticated) {
next({ name: 'Login' })
} else if (to.meta.guest && authStore.isAuthenticated) {
next({ name: 'Home' })
} else {
next()
}
})
export default router
Programmatic Navigation
import { useRouter, useRoute } from 'vue-router'
export default {
setup() {
const router = useRouter()
const route = useRoute()
const navigateToUser = (userId) => {
router.push({ name: 'UserDetail', params: { id: userId } })
}
const goBack = () => {
router.go(-1)
}
const navigateWithQuery = () => {
router.push({
name: 'Users',
query: { page: 1, limit: 10, search: 'john' }
})
}
// Watch route changes
watch(() => route.params.id, (newId) => {
if (newId) {
fetchUserData(newId)
}
})
return {
navigateToUser,
goBack,
navigateWithQuery
}
}
}
Form Handling and Validation
Form with Validation
<template>
<form @submit.prevent="handleSubmit" class="user-form">
<div class="form-group">
<label for="name">Name</label>
<input
id="name"
v-model="form.name"
type="text"
:class="{ error: errors.name }"
@blur="validateField('name')"
>
<span v-if="errors.name" class="error-message">{{ errors.name }}</span>
</div>
<div class="form-group">
<label for="email">Email</label>
<input
id="email"
v-model="form.email"
type="email"
:class="{ error: errors.email }"
@blur="validateField('email')"
>
<span v-if="errors.email" class="error-message">{{ errors.email }}</span>
</div>
<div class="form-group">
<label for="bio">Bio</label>
<textarea
id="bio"
v-model="form.bio"
:class="{ error: errors.bio }"
@blur="validateField('bio')"
></textarea>
<span v-if="errors.bio" class="error-message">{{ errors.bio }}</span>
</div>
<button type="submit" :disabled="!isFormValid || submitting">
{{ submitting ? 'Saving...' : 'Save User' }}
</button>
</form>
</template>
<script>
import { reactive, computed, ref } from 'vue'
export default {
props: {
initialData: {
type: Object,
default: () => ({})
}
},
emits: ['submit'],
setup(props, { emit }) {
const form = reactive({
name: props.initialData.name || '',
email: props.initialData.email || '',
bio: props.initialData.bio || ''
})
const errors = reactive({})
const submitting = ref(false)
const validationRules = {
name: (value) => {
if (!value) return 'Name is required'
if (value.length < 2) return 'Name must be at least 2 characters'
return null
},
email: (value) => {
if (!value) return 'Email is required'
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
if (!emailRegex.test(value)) return 'Invalid email format'
return null
},
bio: (value) => {
if (value && value.length > 500) return 'Bio must be less than 500 characters'
return null
}
}
const validateField = (field) => {
const rule = validationRules[field]
if (rule) {
const error = rule(form[field])
if (error) {
errors[field] = error
} else {
delete errors[field]
}
}
}
const validateForm = () => {
Object.keys(validationRules).forEach(validateField)
}
const isFormValid = computed(() => {
return Object.keys(errors).length === 0 &&
form.name &&
form.email
})
const handleSubmit = async () => {
validateForm()
if (!isFormValid.value) return
submitting.value = true
try {
await emit('submit', { ...form })
} catch (error) {
console.error('Form submission error:', error)
} finally {
submitting.value = false
}
}
return {
form,
errors,
submitting,
isFormValid,
validateField,
handleSubmit
}
}
}
</script>
Testing Vue Applications
Component Testing with Vue Test Utils
import { mount } from '@vue/test-utils'
import { describe, it, expect, vi } from 'vitest'
import UserCard from '@/components/UserCard.vue'
describe('UserCard', () => {
const mockUser = {
id: 1,
name: 'John Doe',
email: 'john@example.com',
status: 'online',
avatar: 'avatar.jpg'
}
it('renders user information correctly', () => {
const wrapper = mount(UserCard, {
props: { user: mockUser }
})
expect(wrapper.find('h3').text()).toBe('John Doe')
expect(wrapper.find('p').text()).toBe('john@example.com')
expect(wrapper.find('.status').classes()).toContain('status-online')
})
it('emits edit event when edit button is clicked', async () => {
const wrapper = mount(UserCard, {
props: { user: mockUser }
})
await wrapper.find('.btn-primary').trigger('click')
expect(wrapper.emitted('edit')).toBeTruthy()
expect(wrapper.emitted('edit')[0]).toEqual([mockUser])
})
it('shows confirmation before delete', async () => {
const confirmSpy = vi.spyOn(window, 'confirm').mockReturnValue(true)
const wrapper = mount(UserCard, {
props: { user: mockUser }
})
await wrapper.find('.btn-danger').trigger('click')
expect(confirmSpy).toHaveBeenCalledWith('Delete user John Doe?')
expect(wrapper.emitted('delete')).toBeTruthy()
expect(wrapper.emitted('delete')[0]).toEqual([1])
confirmSpy.mockRestore()
})
})
Composable Testing
import { describe, it, expect, vi } from 'vitest'
import { useUsers } from '@/composables/useUsers'
// Mock fetch
global.fetch = vi.fn()
describe('useUsers', () => {
beforeEach(() => {
fetch.mockClear()
})
it('fetches users successfully', async () => {
const mockUsers = [
{ id: 1, name: 'John', status: 'online' },
{ id: 2, name: 'Jane', status: 'offline' }
]
fetch.mockResolvedValueOnce({
ok: true,
json: async () => mockUsers
})
const { users, loading, fetchUsers } = useUsers()
expect(loading.value).toBe(false)
await fetchUsers()
expect(users.value).toEqual(mockUsers)
expect(loading.value).toBe(false)
})
it('handles fetch error', async () => {
fetch.mockRejectedValueOnce(new Error('Network error'))
const { error, fetchUsers } = useUsers()
await fetchUsers()
expect(error.value).toBe('Network error')
})
})
Career Opportunities in India (2026)
Salary Expectations
- Junior Vue.js Developer (0-2 years): ₹3-6 LPA
- Mid-Level Developer (2-5 years): ₹6-14 LPA
- Senior Developer (5+ years): ₹14-28 LPA
- Vue.js Architect: ₹22-40 LPA
Top Hiring Companies
Product Companies: Razorpay, Freshworks, Zoho, Dream11 Startups: Meesho, CRED, ShareChat, Unacademy Service Companies: TCS, Infosys, Wipro, HCL
Skills in High Demand
- Vue 3 Composition API
- Pinia state management
- Nuxt.js for SSR/SSG
- Vue ecosystem (Vuetify, Quasar)
- Testing with Vitest/Jest
Learning Roadmap
Beginner (0-3 months)
- JavaScript ES6+ fundamentals
- Vue.js basics and template syntax
- Component communication
- Vue Router basics
Intermediate (3-6 months)
- Composition API mastery
- State management with Pinia
- Form handling and validation
- API integration
Advanced (6+ months)
- Custom composables
- Performance optimization
- Testing strategies
- Nuxt.js framework
Conclusion
Vue.js offers an excellent balance of simplicity and power, making it ideal for developers seeking productivity without sacrificing flexibility. The Composition API and modern tooling position Vue as a strong choice for 2026 projects.
Success in Vue.js requires understanding reactive programming, component architecture, and the ecosystem tools. Focus on building real applications and contributing to the Vue community to advance your career.
The Indian market shows growing adoption of Vue.js, particularly in startups and mid-size companies. Vue developers enjoy competitive salaries and diverse opportunities across industries.
Start your Vue.js journey with Miraclin Technologies' comprehensive program featuring hands-on projects, modern best practices, and career guidance for frontend development success.