Jesse A. Green / notebook

Live demo: the DocCompiler guide library as a static, read-only snapshot (October 2026). No backend, no accounts, nothing to submit.

Vue.js 2 User Guide

Generated May 2026 from vuejs/vue at 9e88707

AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it.

1. Quick Start

Vue.js is a progressive JavaScript framework for building user interfaces. It provides a reactive data-binding system with a component-oriented architecture that makes it straightforward to build everything from simple widgets to complex single-page applications.

Key features:

  • Reactive data binding with automatic DOM updates
  • Component-based architecture with props, events, and slots
  • Template syntax with directives (v-if, v-for, v-model, v-on, v-bind)
  • Computed properties and watchers for derived state
  • Transition and animation system for enter/leave effects
  • Vue 3 Composition API backported (ref, reactive, computed, watch, setup())
  • Single File Components (.vue files) with scoped CSS
  • Server-side rendering via vue-server-renderer

Prerequisites: Node.js and a package manager (npm, yarn, or pnpm).

Install and verify:

npm install vue@2.7.16

Minimal example:

<div id="app">{{ message }}</div>

<script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
<script>
new Vue({
  el: '#app',
  data: {
    message: 'Hello Vue!'
  }
})
</script>
Hello Vue!

Tip

For production, use vue.min.js or the runtime-only build (vue.runtime.common.js) for smaller bundle size. The full build includes the template compiler; the runtime build requires pre-compiled templates via a build tool.

What to try next: explore the Template Syntax section for directives, then build your first component.

2. Installation

System Requirements

Requirement Minimum
Node.js 12+ (for build tooling)
Browser IE9+ (with polyfills), all modern browsers
Package Manager npm, yarn, or pnpm

Installation Methods

npm

npm install vue@2.7.16

yarn

yarn add vue@2.7.16

pnpm

pnpm add vue@2.7.16

CDN

<!-- Development (includes warnings) -->
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>

<!-- Production (minified) -->
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.min.js"></script>

Build Variants

Build File Includes Compiler Use Case
Full (UMD) dist/vue.js Yes CDN, direct <script>
Runtime (UMD) dist/vue.runtime.js No With build tools
Full (CommonJS) dist/vue.common.js Yes Bundlers (older)
Runtime (CommonJS) dist/vue.runtime.common.js No Bundlers (default main)
Full (ESM) dist/vue.esm.js Yes Bundlers (ESM)
Runtime (ESM) dist/vue.runtime.esm.js No Bundlers (default module)
Runtime (MJS) dist/vue.runtime.mjs No Node.js ESM import

Important

The runtime-only build is approximately 30% lighter than the full build. If you use a build tool (webpack, Vite, Rollup), use the runtime build and let vue-loader or @vitejs/plugin-vue2 pre-compile your templates. The full build is needed only when compiling templates in the browser at runtime (e.g., passing a string to the template option).

Post-Install Verification

const Vue = require('vue')
console.log(Vue.version)
2.7.16

Additional Packages

Package Install Purpose
@vue/compiler-sfc Included with vue SFC compilation (.vue files)
vue-server-renderer npm install vue-server-renderer@2.7.16 Server-side rendering
vue-template-compiler npm install vue-template-compiler@2.7.16 Standalone template compilation

Warning

The version of vue-server-renderer and vue-template-compiler must match the version of vue exactly. A version mismatch causes a startup error: Vue packages version mismatch.

3. Configuration

Vue exposes a global configuration object at Vue.config that controls framework-wide behavior. Set individual properties — do not replace the entire object.

Configuration Reference

Option Type Default Description
silent boolean false Suppress all Vue warnings
productionTip boolean true (dev) Show production mode tip on boot
devtools boolean true (dev) Enable Vue DevTools inspection
performance boolean false Enable component init/compile/render performance tracing in DevTools
errorHandler Function null Global error handler for uncaught errors in components
warnHandler Function null Custom handler for Vue warnings
ignoredElements Array<string|RegExp> [] Ignore custom elements outside Vue (e.g., Web Components)
keyCodes Object {} Custom key alias(es) for v-on keyboard events
optionMergeStrategies Object {} Custom merge strategies for component options
async boolean true Async update batching (set false only for testing)
// Suppress all warnings
Vue.config.silent = true

// Global error handler
Vue.config.errorHandler = function (err, vm, info) {
  console.error(`Error in ${info}:`, err)
}

// Custom warning handler
Vue.config.warnHandler = function (msg, vm, trace) {
  console.warn(`[Custom warn]: ${msg}${trace}`)
}

// Ignore Web Components
Vue.config.ignoredElements = ['my-web-component', /^ion-/]

// Custom key aliases for v-on
Vue.config.keyCodes = {
  f1: 112,
  mediaPlayPause: 179
}

Note

productionTip and devtools are automatically disabled in production builds (when process.env.NODE_ENV === 'production'). Setting Vue.config.performance = true requires a browser that supports the performance.mark API.

See the Admin Guide for the complete internal configuration reference and option merge strategy details.

4. Core Usage

Creating a Vue Instance

Every Vue application starts by creating a root Vue instance:

const vm = new Vue({
  el: '#app',
  data: {
    message: 'Hello',
    count: 0
  },
  computed: {
    reversedMessage() {
      return this.message.split('').reverse().join('')
    }
  },
  methods: {
    increment() {
      this.count++
    }
  }
})

Tip

When el is provided, Vue mounts automatically. Otherwise, call vm.$mount('#app') manually to control mount timing.

Template Syntax

Vue uses an HTML-based template syntax with special directives:

Text Interpolation

<span>{{ message }}</span>
<span v-once>This will never change: {{ message }}</span>
<span v-html="rawHtml"></span>
<span v-text="message"></span>

Warning

v-html renders raw HTML and can expose your application to XSS attacks. Only use it with trusted content — never on user-supplied input.

Directives Reference

Directive Purpose Example
v-bind (:) Bind attribute to expression :href="url"
v-on (@) Attach event listener @click="handler"
v-model Two-way data binding v-model="searchText"
v-if Conditional rendering (removes from DOM) v-if="isVisible"
v-else-if Else-if chain v-else-if="type === 'B'"
v-else Else block v-else
v-show Toggle visibility (CSS display) v-show="isActive"
v-for List rendering v-for="item in items"
v-slot (#) Named/scoped slot v-slot:header
v-pre Skip compilation for this element v-pre
v-cloak Hide until compiled v-cloak
v-once Render once, skip future updates v-once

Conditional Rendering

<div v-if="type === 'A'">Type A</div>
<div v-else-if="type === 'B'">Type B</div>
<div v-else>Other</div>

<!-- v-show toggles CSS display (element stays in DOM) -->
<div v-show="isVisible">Toggled by CSS</div>

List Rendering

<ul>
  <li v-for="(item, index) in items" :key="item.id">
    {{ index }}: {{ item.name }}
  </li>
</ul>

<!-- Object iteration -->
<div v-for="(value, key, index) in object" :key="key">
  {{ key }}: {{ value }}
</div>

Important

Always provide a unique :key attribute when using v-for. Without it, Vue uses an "in-place patch" strategy that can cause subtle bugs with stateful components or transitions.

Event Handling

<!-- Method handler -->
<button @click="increment">+1</button>

<!-- Inline handler -->
<button @click="count++">+1</button>

<!-- Event modifiers -->
<form @submit.prevent="onSubmit">...</form>
<a @click.stop="doThis">Stop propagation</a>
<input @keyup.enter="submit">
<button @click.once="doOnce">Once only</button>

<!-- Key modifiers -->
<input @keyup.enter="submit">
<input @keyup.esc="cancel">

Event modifiers: .stop, .prevent, .capture, .self, .once, .passive

Key modifiers: .enter, .tab, .delete, .esc, .space, .up, .down, .left, .right

System modifiers: .ctrl, .alt, .shift, .meta, .exact

Two-Way Binding with v-model

<!-- Text input -->
<input v-model="message" placeholder="edit me">

<!-- Checkbox -->
<input type="checkbox" v-model="checked">

<!-- Radio -->
<input type="radio" v-model="picked" value="a">

<!-- Select -->
<select v-model="selected">
  <option value="">Choose</option>
  <option value="A">A</option>
</select>

<!-- Modifiers -->
<input v-model.lazy="msg">    <!-- sync on change instead of input -->
<input v-model.number="age">  <!-- auto-typecast to number -->
<input v-model.trim="msg">    <!-- trim whitespace -->

Reactivity System

Vue's reactivity system uses Object.defineProperty to convert data properties into getter/setter pairs. When a property is accessed during rendering, it is tracked as a dependency. When it changes, dependent watchers are notified and the view updates.

Reactivity caveats — arrays:

Vue intercepts these array mutation methods to trigger reactivity: push, pop, shift, unshift, splice, sort, reverse.

Setting an item by index or changing length directly is not reactive:

// NOT reactive:
this.items[index] = newValue
this.items.length = newLength

// Use instead:
Vue.set(this.items, index, newValue)     // or this.$set(...)
this.items.splice(newLength)

Reactivity caveats — objects:

Vue cannot detect property addition or deletion on already-created objects:

// NOT reactive (adding new property):
this.obj.newProp = 'value'

// Use instead:
Vue.set(this.obj, 'newProp', 'value')    // or this.$set(...)

// Removing:
Vue.delete(this.obj, 'prop')             // or this.$delete(...)

Warning

Vue.set and Vue.delete (aliased as this.$set / this.$delete) are essential when adding or removing properties from reactive objects or setting array items by index. Without them, the UI will not update.

Computed Properties and Watchers

new Vue({
  data: {
    firstName: 'John',
    lastName: 'Doe',
    question: ''
  },
  computed: {
    // Getter only
    fullName() {
      return `${this.firstName} ${this.lastName}`
    },
    // Getter + setter
    fullNameRW: {
      get() { return `${this.firstName} ${this.lastName}` },
      set(newValue) {
        const names = newValue.split(' ')
        this.firstName = names[0]
        this.lastName = names[names.length - 1]
      }
    }
  },
  watch: {
    // Simple watcher
    question(newVal, oldVal) {
      this.fetchAnswer()
    },
    // Deep watcher
    someObject: {
      handler(newVal) { /* ... */ },
      deep: true
    },
    // Immediate watcher (fires on creation)
    searchQuery: {
      handler(val) { this.search(val) },
      immediate: true
    }
  }
})

Components

// Global registration
Vue.component('my-component', {
  props: {
    title: { type: String, required: true },
    count: { type: Number, default: 0 }
  },
  template: '<div>{{ title }}: {{ count }}</div>'
})

// Local registration
const ChildComponent = {
  props: ['message'],
  template: '<span>{{ message }}</span>'
}

new Vue({
  components: { ChildComponent },
  template: '<child-component message="hello" />'
})

Props

props: {
  // Type check
  age: Number,
  // Multiple types
  value: [String, Number],
  // Required with type
  name: { type: String, required: true },
  // Default value
  count: { type: Number, default: 0 },
  // Default function (for objects/arrays)
  items: { type: Array, default: () => [] },
  // Custom validator
  status: {
    type: String,
    validator: (val) => ['active', 'inactive'].includes(val)
  }
}

Events

<!-- Parent -->
<child-component @update="handleUpdate" />

<!-- Child -->
<script>
export default {
  methods: {
    save() {
      this.$emit('update', this.localData)
    }
  }
}
</script>

Slots

<!-- Parent -->
<layout-component>
  <template #header>
    <h1>Page Title</h1>
  </template>
  <template #default>
    <p>Main content</p>
  </template>
  <template #footer="{ year }">
    <p>&copy; {{ year }}</p>
  </template>
</layout-component>

<!-- Child (LayoutComponent) -->
<div>
  <header><slot name="header"></slot></header>
  <main><slot></slot></main>
  <footer><slot name="footer" :year="currentYear"></slot></footer>
</div>

Lifecycle Hooks

Components go through a defined lifecycle. Use hooks to run code at specific stages:

Hook When It Fires
beforeCreate Instance initialized, before data/events setup
created Instance created, data/computed/methods available, no DOM yet
beforeMount Before initial render
mounted DOM rendered, this.$el available
beforeUpdate Data changed, before DOM re-render
updated DOM re-rendered after data change
beforeDestroy Before teardown
destroyed Instance destroyed, all bindings/listeners removed
activated <keep-alive> component activated
deactivated <keep-alive> component deactivated
errorCaptured Error from descendant component captured
serverPrefetch SSR only — resolved before server render
renderTracked Reactive dependency tracked (dev only)
renderTriggered Re-render triggered by dependency change (dev only)

Filters

Vue 2 supports template filters for text formatting:

// Global filter
Vue.filter('capitalize', function (value) {
  if (!value) return ''
  return value.charAt(0).toUpperCase() + value.slice(1)
})

// Local filter
new Vue({
  filters: {
    truncate(v) {
      const newline = v.indexOf('\n')
      return newline > 0 ? v.slice(0, newline) : v
    },
    formatDate(v) {
      return v.replace(/T|Z/g, ' ')
    },
    pluralize(n) {
      return n === 1 ? 'item' : 'items'
    }
  }
})
<!-- In mustaches -->
{{ message | capitalize }}

<!-- In v-bind -->
<div :id="rawId | formatId"></div>

<!-- Chained -->
{{ message | capitalize | truncate }}

Custom Directives

// Global directive
Vue.directive('focus', {
  inserted(el) {
    el.focus()
  }
})

// Shorthand (bind + update)
Vue.directive('color', function (el, binding) {
  el.style.color = binding.value
})
<input v-focus>
<span v-color="'red'">Colored text</span>

Directive hook functions: bind, inserted, update, componentUpdated, unbind

Plugins

// Using a plugin
Vue.use(MyPlugin, { someOption: true })

// Plugin structure
const MyPlugin = {
  install(Vue, options) {
    Vue.mixin({ /* ... */ })
    Vue.directive('my-directive', { /* ... */ })
    Vue.prototype.$myMethod = function () { /* ... */ }
  }
}

Note

Vue.use() automatically prevents double-installation of the same plugin. Calling it multiple times with the same plugin has no additional effect.

Mixins

const myMixin = {
  created() {
    console.log('mixin hook called')
  },
  methods: {
    sharedMethod() { /* ... */ }
  }
}

// Local mixin
new Vue({
  mixins: [myMixin]
})

// Global mixin (affects ALL instances — use with caution)
Vue.mixin({
  created() { /* runs in every component */ }
})

Transitions and Animations

<transition name="fade">
  <p v-if="show">Hello</p>
</transition>

<transition-group name="list" tag="ul">
  <li v-for="item in items" :key="item.id">{{ item.text }}</li>
</transition-group>
.fade-enter-active, .fade-leave-active {
  transition: opacity 0.5s;
}
.fade-enter, .fade-leave-to {
  opacity: 0;
}

Vue applies CSS classes during enter/leave transitions:

Class When Applied
{name}-enter Starting state for enter
{name}-enter-active Active state during enter
{name}-enter-to Ending state for enter
{name}-leave Starting state for leave
{name}-leave-active Active state during leave
{name}-leave-to Ending state for leave

Built-in <keep-alive> Component

<keep-alive> caches inactive component instances instead of destroying them:

<!-- Cache all -->
<keep-alive>
  <component :is="currentView"></component>
</keep-alive>

<!-- Include/exclude by name -->
<keep-alive include="ComponentA,ComponentB">
  <router-view></router-view>
</keep-alive>

<!-- Regex or array -->
<keep-alive :include="/^Todo/">
  <component :is="view"></component>
</keep-alive>

<!-- Max cached instances (LRU eviction) -->
<keep-alive :max="10">
  <component :is="view"></component>
</keep-alive>
Prop Type Description
include String | RegExp | Array Only cache matching component names
exclude String | RegExp | Array Never cache matching component names
max String | Number Maximum number of cached instances

Cached components receive activated and deactivated lifecycle hooks instead of mounted/destroyed.

5. Common Tasks

Composition API (Vue 2.7)

Vue 2.7 backports the Composition API from Vue 3. Use setup() to organize logic by concern rather than by option type:

import { ref, computed, watch, onMounted, defineComponent } from 'vue'

export default defineComponent({
  setup() {
    const count = ref(0)
    const doubled = computed(() => count.value * 2)

    function increment() {
      count.value++
    }

    watch(count, (newVal, oldVal) => {
      console.log(`count changed: ${oldVal} → ${newVal}`)
    })

    onMounted(() => {
      console.log('component mounted')
    })

    return { count, doubled, increment }
  }
})

Tip

<script setup> syntax is supported in Vue 2.7 SFCs via @vue/compiler-sfc. It provides a more concise way to use the Composition API.

Composition API Functions Available in Vue 2.7

Reactivity:

Function Purpose
ref(value) Create a reactive reference
shallowRef(value) Shallow reactive reference
computed(getter) Computed value
reactive(object) Deep reactive object
shallowReactive(object) Shallow reactive object
readonly(object) Deep readonly proxy
shallowReadonly(object) Shallow readonly proxy
toRef(obj, key) Create ref from reactive property
toRefs(obj) Convert reactive object to plain object of refs
unref(ref) Unwrap ref (returns .value or the value itself)
isRef(value) Check if value is a ref
isReactive(value) Check if value is reactive
isReadonly(value) Check if value is readonly
isProxy(value) Check if value is reactive or readonly
triggerRef(ref) Force trigger a shallow ref
customRef(factory) Create ref with custom track/trigger
markRaw(object) Mark object to never be made reactive
toRaw(proxy) Get the raw object from a reactive proxy

Watchers:

Function Purpose
watch(source, cb, options?) Watch reactive source(s) with callback
watchEffect(effect) Run effect immediately and re-run on dependency change
watchPostEffect(effect) watchEffect with flush: 'post'
watchSyncEffect(effect) watchEffect with flush: 'sync'

Watch options: immediate (fire on creation), deep (deep watch), flush ('pre' | 'post' | 'sync').

Lifecycle hooks (Composition API):

Composition API Options API Equivalent
onBeforeMount beforeMount
onMounted mounted
onBeforeUpdate beforeUpdate
onUpdated updated
onBeforeUnmount beforeDestroy
onUnmounted destroyed
onActivated activated
onDeactivated deactivated
onErrorCaptured errorCaptured
onServerPrefetch serverPrefetch
onRenderTracked renderTracked
onRenderTriggered renderTriggered

Dependency Injection:

import { provide, inject } from 'vue'

// Parent
setup() {
  provide('theme', 'dark')
}

// Child (any depth)
setup() {
  const theme = inject('theme', 'light') // default: 'light'
}

Effect Scope:

import { effectScope, onScopeDispose } from 'vue'

const scope = effectScope()
scope.run(() => {
  const counter = ref(0)
  watch(counter, () => { /* ... */ })
  onScopeDispose(() => { console.log('scope disposed') })
})
scope.stop() // disposes all effects in scope

Note

The suspensible option for defineAsyncComponent is not supported in Vue 2 and is silently ignored. The useListeners() helper is Vue 2-only (Vue 3 merged listeners into $attrs).

Provide/Inject (Options API)

// Parent component
export default {
  provide() {
    return {
      theme: this.theme
    }
  }
}

// Descendant component (any depth)
export default {
  inject: ['theme']
}

Async Components

// Simple async component
Vue.component('async-comp', () => import('./MyComponent.vue'))

// With loading/error states (Vue 2.7 defineAsyncComponent)
import { defineAsyncComponent } from 'vue'

const AsyncComp = defineAsyncComponent({
  loader: () => import('./HeavyComponent.vue'),
  loadingComponent: LoadingSpinner,
  errorComponent: ErrorDisplay,
  delay: 200,       // delay before showing loading (ms)
  timeout: 10000,   // timeout before showing error (ms)
  onError(error, retry, fail, attempts) {
    if (attempts <= 3) retry()
    else fail()
  }
})

Using Vue.extend for Subclasses

const Profile = Vue.extend({
  template: '<p>{{ fullName }}</p>',
  data() {
    return { firstName: 'Jane', lastName: 'Doe' }
  },
  computed: {
    fullName() { return `${this.firstName} ${this.lastName}` }
  }
})

// Create an instance
new Profile().$mount('#profile')

Programmatic Event Bus

// Create a shared event bus
const EventBus = new Vue()

// Component A: emit
EventBus.$emit('item-added', { id: 1 })

// Component B: listen
EventBus.$on('item-added', (item) => {
  console.log('Received:', item)
})

// Clean up (in beforeDestroy)
EventBus.$off('item-added')

Vue.observable for Lightweight State

const state = Vue.observable({
  count: 0,
  message: 'hello'
})

// Use in any component
new Vue({
  computed: {
    count: () => state.count
  },
  methods: {
    increment() { state.count++ }
  }
})

Using nextTick for DOM Updates

this.message = 'updated'
this.$nextTick(() => {
  // DOM is now updated
  console.log(this.$el.textContent) // 'updated'
})

// Or with async/await
async handler() {
  this.message = 'updated'
  await this.$nextTick()
  // DOM updated
}

// Static method
Vue.nextTick(() => { /* ... */ })

Error Handling

// Global error handler
Vue.config.errorHandler = function (err, vm, info) {
  // info is a Vue-specific error info, e.g. which lifecycle hook
  console.error('Global error:', err, info)
  // Report to tracking service
}

// Component-level with errorCaptured
export default {
  errorCaptured(err, childVm, info) {
    console.error('Caught from child:', err)
    return false // stop propagation
  }
}

// Composition API
import { onErrorCaptured } from 'vue'
setup() {
  onErrorCaptured((err, instance, info) => {
    console.error(err)
    return false
  })
}

Render Functions and h()

// Options API render function
export default {
  render(createElement) {
    return createElement('div', { class: 'container' }, [
      createElement('h1', this.title),
      this.$slots.default
    ])
  }
}

// Composition API h()
import { h } from 'vue'
export default {
  setup() {
    return () => h('div', { class: 'container' }, 'Hello')
  }
}

Functional Components

Vue.component('functional-comp', {
  functional: true,
  props: ['level'],
  render(createElement, context) {
    return createElement(
      `h${context.props.level}`,
      context.data,
      context.children
    )
  }
})

6. Troubleshooting

Error: Vue packages version mismatch

Cause: vue-server-renderer or vue-template-compiler version does not match the vue package version.

Solution:

  1. Check installed versions:
    npm ls vue vue-server-renderer vue-template-compiler
    
  2. Align all versions to the same release:
    npm install vue@2.7.16 vue-server-renderer@2.7.16 vue-template-compiler@2.7.16
    

Prevention: Pin all three packages to the same exact version in package.json.

Error: Property or method "X" is not defined on the instance

Cause: You referenced a property in the template that is not declared in data, computed, methods, or props.

Solution:

  1. Verify the property name matches exactly (case-sensitive)
  2. Ensure the property is declared in the correct option (data, computed, or methods)
  3. If using Composition API, ensure you return the property from setup()

Prevention: Always declare all template-referenced properties in your component definition.

Error: Avoid mutating a prop directly

Cause: A child component is modifying a prop passed from its parent.

Solution:

  1. Use a local data property initialized from the prop:
    data() {
      return { localValue: this.initialValue }
    }
    
  2. Or use a computed property to transform the prop:
    computed: {
      normalizedValue() { return this.value.trim().toLowerCase() }
    }
    

Prevention: Follow one-way data flow — props down, events up.

Error: Do not replace the Vue.config object

Cause: Code is assigning a new object to Vue.config rather than setting individual properties.

Solution: Set properties individually:

// Wrong:
Vue.config = { silent: true }

// Right:
Vue.config.silent = true

Warning: Event "X" is emitted but the handler is registered for "Y"

Cause: Case mismatch between emitted event name and the listener in a template. HTML attributes are case-insensitive, so camelCase event names don't work in in-DOM templates. (GitHub issue #13287)

Solution: Use kebab-case for event names:

// Emit
this.$emit('item-selected', data)
<!-- Listen -->
<child @item-selected="handleSelect" />

Reactivity not working — Vue.set / this.$set needed

Cause: Adding a new property to an already-reactive object or setting an array item by index does not trigger updates because Object.defineProperty can only intercept existing properties.

Solution:

// Objects: use Vue.set
this.$set(this.obj, 'newKey', 'value')

// Arrays: use Vue.set or splice
this.$set(this.items, index, newValue)
this.items.splice(index, 1, newValue)

Warning: Vue is a constructor and should be called with the new keyword

Cause: Calling Vue() as a function instead of new Vue().

Solution: Always use the new keyword:

const vm = new Vue({ /* options */ })

SFC compilation error: SFC contains no <script> tags

Cause: @vue/compiler-sfc's compileScript() was called on a .vue file that has no <script> block.

Solution: Ensure your .vue file contains a <script> or <script setup> block. If the component is template-only, add a minimal script block:

<script>
export default {}
</script>

<keep-alive> not caching components

Cause: The component is missing a name option, or the include/exclude pattern does not match the component's registered name.

Solution:

  1. Add a name option to cached components
  2. Verify include/exclude patterns match the component name string
  3. Check that max is not set too low, causing LRU eviction

Source maps broken in Firefox

Cause: Known issue with Vue 2 source map generation in certain browser debugging scenarios. (GitHub issue #12689)

Solution: Use Chrome DevTools for debugging, or disable source maps in your build configuration if Firefox debugging is critical.

Race condition in reactive updates

Cause: Potential race condition reported in the reactivity system. (GitHub issue #13309)

Solution: Ensure reactive mutations are performed synchronously within the same tick. Avoid concurrent async operations that mutate the same reactive data without serialization.

Memory leaks with large component trees

Cause: Components or watchers not being properly cleaned up. (GitHub issue #13308)

Solution:

  1. Clean up event listeners and timers in beforeDestroy
  2. Remove event bus listeners: EventBus.$off('event', this.handler)
  3. Cancel pending async operations in beforeDestroy

7. FAQ

Q: What is the difference between Vue 2 and Vue 3?

Vue 3 introduced Proxy-based reactivity (replacing Object.defineProperty), the Composition API, <Teleport>, <Suspense>, improved TypeScript support, and multiple root nodes in templates (fragments). Vue 2.7 backports the Composition API but retains the Object.defineProperty-based reactivity system. Vue 2 reached End of Life on December 31, 2023.

Q: Can I use the Composition API in Vue 2?

Yes. Vue 2.7 includes native support for setup(), ref, reactive, computed, watch, provide/inject, lifecycle hooks (onMounted, etc.), and <script setup> in SFCs. No additional plugin is needed.

Q: When should I use v-if vs v-show?

Use v-if when the condition rarely changes — it has higher toggle cost (adds/removes elements from DOM) but no initial render cost for hidden elements. Use v-show when toggling frequently — it has higher initial render cost (always renders) but very cheap toggle cost (CSS only).

Q: How do I register a component globally vs locally?

Global: Vue.component('name', definition) — available in all templates. Local: add to the components option of the parent component. Prefer local registration to keep bundle size down with tree-shaking.

Q: What is Vue.extend used for?

Vue.extend creates a reusable component constructor (a "subclass" of Vue). It is used internally by Vue.component when you pass a plain object, and can be used directly for programmatic component instantiation (e.g., modals, toasts).

Q: How do I handle errors from async components?

Use defineAsyncComponent with an onError callback. The callback receives the error, retry() and fail() functions, and the attempt count, letting you implement retry logic.

Q: What is Vue.observable and when should I use it?

Vue.observable(obj) makes a plain object reactive — useful for lightweight shared state across components without Vuex. Available since Vue 2.6.

Q: How does nextTick work?

Vue.nextTick(cb) / this.$nextTick(cb) defers the callback until after the next DOM update cycle. Vue batches data changes and updates the DOM asynchronously, so use nextTick when you need to read or manipulate the DOM immediately after a data change.

Q: Can I use TypeScript with Vue 2?

Yes. Vue 2.7 ships TypeScript type definitions at types/index.d.ts. Use defineComponent() to enable proper type inference for component options. <script setup lang="ts"> is supported in SFCs with @vue/compiler-sfc.

Q: How do provide and inject work?

provide makes data available to all descendant components (any depth). inject retrieves that data. In the Options API, use them as component options. In the Composition API (Vue 2.7), import provide() and inject() from vue and call them in setup(). The prototype chain is used internally — child provides shadow parent provides.