Miraclin Technologies
← Back to Blog
Vue.jsFrontend DevelopmentJavaScriptComposition APIPinia

Vue.js Development Guide 2026: Progressive Frontend Framework

Miraclin Technologies
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)

  1. JavaScript ES6+ fundamentals
  2. Vue.js basics and template syntax
  3. Component communication
  4. Vue Router basics

Intermediate (3-6 months)

  1. Composition API mastery
  2. State management with Pinia
  3. Form handling and validation
  4. API integration

Advanced (6+ months)

  1. Custom composables
  2. Performance optimization
  3. Testing strategies
  4. 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.