Skip to main content
โšก Calmops

Vue 4 Complete Guide: Building Modern Web Applications in 2026

Introduction

Vue.js has come a long way since its initial release in 2014. Vue 3 introduced the Composition API and Script Setup, fundamentally changed how developers write Vue applications. Now, Vue 4 takes everything to the next level with Vapor Mode, improved reactivity, and a host of developer experience improvements.

In this comprehensive guide, we’ll explore everything you need to know about Vue 4. From the revolutionary Vapor Mode that eliminates the virtual DOM overhead to the new devtools features, you’ll learn how to build faster, more efficient applications with Vue 4.


What’s New in Vue 4

The Big Changes

Vue 4 introduces several groundbreaking features:

Feature Description Impact
Vapor Mode Eliminated virtual DOM for select components 10-100x performance improvement
Improved Reactivity Fine-grained reactivity with proxies Faster updates, less memory
Native Props TypeScript-first props without runtime overhead Better TypeScript support
Improved DevTools Better debugging experience Faster development

Why Vapor Mode Matters

The biggest change in Vue 4 is Vapor Mode. Unlike the traditional Vue rendering pipeline that uses a virtual DOM, Vapor Mode compiles your templates to direct DOM operations:

// Traditional Vue 3 (virtual DOM)
// Updates require diffing and reconciliation

// Vue 4 Vapor Mode (direct DOM)
// Compiles to efficient direct mutations

This means:

  • No virtual DOM overhead
  • Automatic batching of updates
  • Better memory efficiency
  • Near-native performance

Getting Started with Vue 4

Installation

Create a new Vue 4 project:

# Using npm
npm create vue@latest my-vue-app
cd my-vue-app
npm install

# Select Vue 4 when prompted

Project Structure

my-vue-app/
โ”œโ”€โ”€ src/
โ”‚   โ”œโ”€โ”€ components/
โ”‚   โ”‚   โ””โ”€โ”€ Counter.vue
โ”‚   โ”œโ”€โ”€ composables/
โ”‚   โ”‚   โ””โ”€โ”€ useCounter.ts
โ”‚   โ”œโ”€โ”€ App.vue
โ”‚   โ””โ”€โ”€ main.ts
โ”œโ”€โ”€ index.html
โ”œโ”€โ”€ package.json
โ””โ”€โ”€ vite.config.ts

Vue 4 Basics

Script Setup Improvements

Vue 4’s Script Setup is even more powerful:

<script setup lang="ts">
// Define props with type inference
const { count = 0, label = 'Counter' } = defineProps<{
  count?: number
  label?: string
}>()

// Emit events with full typing
const emit = defineEmits<{
  update: [value: number]
  reset: []
}>()

// Reactive state
const value = $ref(count)
const doubled = $derived(value * 2)

// Methods
function increment() {
  value++
  emit('update', value)
}

function reset() {
  value = 0
  emit('reset')
}
</script>

<template>
  <div class="counter">
    <h2>{{ label }}</h2>
    <p>Count: {{ value }}</p>
    <p>Doubled: {{ doubled }}</p>
    <button @click="increment">Increment</button>
    <button @click="reset">Reset</button>
  </div>
</template>

Understanding Vapor Mode

How Vapor Mode Works

Vapor Mode is optional in Vue 4. You can enable it per-component:

<script setup vapor>
import { ref, computed } from 'vue/vapor'

const count = ref(0)
const doubled = computed(() => count.value * 2)

function increment() {
  count.value++
}
</script>

<template>
  <div>
    <p>Count: {{ count }}</p>
    <p>Doubled: {{ doubled }}</p>
    <button @click="increment">Increment</button>
  </div>
</template>

When to Use Vapor Mode

Vapor Mode is ideal for:

  • Performance-critical components
  • List rendering with large datasets
  • Real-time applications
  • Animation-heavy interfaces

Vapor vs Traditional Mode

Aspect Traditional Vapor
Virtual DOM Yes No
Performance Good Excellent
Memory Moderate Low
Compatibility Full Partial
Bundle Size ~50KB ~30KB

Reactivity System

New Reactivity Primitives

Vue 4 introduces new reactivity primitives:

// $ref - creates a reactive reference
const count = $ref(0)

// $computed - creates a computed value
const doubled = $computed(() => count.value * 2)

// $effect - creates a side effect
$effect(() => {
  console.log(`Count is now: ${count.value}`)
})

// $watch - watches for changes
$watch(count, (newVal, oldVal) => {
  console.log(`Changed from ${oldVal} to ${newVal}`)
})

// $shallowRef - creates a shallow reactive reference
const state = $shallowRef({
  nested: { value: 0 }
})

Fine-Grained Reactivity

Vue 4’s reactivity is more granular:

import { $ref, $effect } from 'vue'

// Only triggers updates when specifically accessed
const state = $ref({
  user: {
    profile: {
      name: 'John',
      email: '[email protected]'
    }
  }
})

// Only re-renders when name changes
$effect(() => {
  console.log(state.user.profile.name)
})

Components in Vue 4

New Component Features

Async Components

<script setup>
import { defineAsyncComponent } from 'vue'

const HeavyComponent = defineAsyncComponent(() => 
  import('./HeavyComponent.vue')
)
</script>

<template>
  <HeavyComponent v-if="show" />
</template>

Suspense

<script setup>
import { ref } from 'vue'

const show = ref(true)
</script>

<template>
  <Suspense>
    <template #default>
      <AsyncComponent />
    </template>
    <template #fallback>
      <LoadingSpinner />
    </template>
  </Suspense>
</template>

State Management

Pinia in Vue 4

Pinia remains the recommended state management solution:

// stores/counter.ts
import { defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', {
  state: () => ({
    count: 0,
    history: [] as number[]
  }),
  
  getters: {
    doubled: (state) => state.count * 2,
    average: (state) => 
      state.history.length > 0
        ? state.history.reduce((a, b) => a + b, 0) / state.history.length
        : 0
  },
  
  actions: {
    increment() {
      this.count++
      this.history.push(this.count)
    },
    decrement() {
      this.count--
      this.history.push(this.count)
    },
    reset() {
      this.count = 0
      this.history = []
    }
  }
})

Using the Store

<script setup lang="ts">
import { useCounterStore } from '@/stores/counter'

const store = useCounterStore()
</script>

<template>
  <div>
    <p>Count: {{ store.count }}</p>
    <p>Doubled: {{ store.doubled }}</p>
    <button @click="store.increment">+</button>
    <button @click="store.decrement">-</button>
    <button @click="store.reset">Reset</button>
  </div>
</template>

TypeScript Integration

Full TypeScript Support

Vue 4 has native TypeScript support:

// types/User.ts
interface User {
  id: number
  name: string
  email: string
  role: 'admin' | 'user' | 'guest'
}

// components/UserCard.vue
<script setup lang="ts">
import type { User } from '@/types/User'

interface Props {
  user: User
  showEmail?: boolean
}

const { user, showEmail = false } = defineProps<Props>()

const emit = defineEmits<{
  select: [user: User]
  delete: [id: number]
}>()

function handleSelect() {
  emit('select', user)
}
</script>

<template>
  <div class="user-card">
    <h3>{{ user.name }}</h3>
    <p v-if="showEmail">{{ user.email }}</p>
    <span class="badge">{{ user.role }}</span>
    <button @click="handleSelect">Select</button>
  </div>
</template>

Vue Router 5

New Router Features

// router/index.ts
import { createRouter, createWebHistory } from 'vue-router'

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    {
      path: '/',
      name: 'home',
      component: () => import('@/views/HomeView.vue')
    },
    {
      path: '/user/:id',
      name: 'user',
      component: () => import('@/views/UserView.vue'),
      props: true,
      children: [
        {
          path: 'profile',
          name: 'user-profile',
          component: () => import('@/views/UserProfile.vue')
        }
      ]
    }
  ]
})

export default router
router.beforeEach((to, from, next) => {
  const isAuthenticated = useAuthStore().isAuthenticated
  
  if (to.meta.requiresAuth && !isAuthenticated) {
    next({ name: 'login', query: { redirect: to.fullPath } })
  } else {
    next()
  }
})

Best Practices

Performance Optimization

<script setup>
// Use shallowRef for large objects
const largeData = $shallowRef(externalLibrary.loadData())

// Use computed for derived state
const sortedItems = $computed(() => 
  [...items.value].sort((a, b) => a.name.localeCompare(b.name))
)

// Memoize expensive computations
import { useMemo } from 'vue-vapor'

const expensiveResult = useMemo(() => 
  heavyComputation(data.value)
, [data])
</script>

<template>
  <!-- Use v-memo for list performance -->
  <div 
    v-for="item in items" 
    :key="item.id"
    v-memo="[item.updated]"
  >
    {{ item.content }}
  </div>
</template>

Component Design

<!-- Good: Single Responsibility -->
<template>
  <UserAvatar :src="user.avatar" :alt="user.name" />
  <UserName :name="user.name" :verified="user.verified" />
  <UserBio :bio="user.bio" />
</template>

<!-- Avoid: God Components -->
<template>
  <!-- Don't put everything in one component -->
  <UserProfile :user="user" />
</template>

Migration from Vue 3

Key Changes

Vue 3 Vue 4
ref() $ref()
computed() $computed()
watch() $watch()
reactive() $state()
defineProps() defineProps<Props>()

Migration Steps

  1. Update dependencies:
npm install vue@4 vue-router@5 pinia@3
  1. Update components:
<!-- Vue 3 -->
<script setup>
import { ref, computed } from 'vue'

const count = ref(0)
const doubled = computed(() => count.value * 2)
</script>

<!-- Vue 4 -->
<script setup>
const count = $ref(0)
const doubled = $computed(() => count * 2)
</script>
  1. Enable Vapor Mode (optional):
<script setup vapor>
// Components now use Vapor rendering
</script>

External Resources

Official Documentation

Learning Resources

Tools


Conclusion

Vue 4 represents a significant evolution in the Vue ecosystem. The introduction of Vapor Mode brings performance that rivals vanilla JavaScript, while the improved developer experience makes building applications more enjoyable than ever.

Key takeaways:

  1. Vapor Mode is optional but powerful: Enable it for performance-critical components
  2. New reactivity primitives: Use $ref, $computed, $effect for cleaner code
  3. TypeScript is first-class: Full type inference out of the box
  4. Migration is straightforward: Most Vue 3 code will work with minimal changes

Whether you’re building a small widget or a large-scale application, Vue 4 provides the tools you need to ship faster with excellent performance.


Comments