| name | debug:vue |
| description | Debug Vue.js 3 application issues systematically. This skill helps diagnose and resolve Vue-specific problems including reactivity failures with ref/reactive, component update issues, Pinia store state management problems, computed property caching bugs, Teleport/Suspense rendering issues, and SSR hydration mismatches. Provides Vue DevTools usage, console debugging techniques, Vite dev server troubleshooting, and vue-tsc type checking guidance. |
Vue.js Debugging Guide
A systematic approach to debugging Vue.js 3 applications, covering common error patterns, debugging tools, and resolution strategies.
Common Error Patterns
1. Reactivity Not Working
Symptoms:
- Data changes but UI does not update
- Computed properties return stale values
- Watch callbacks not firing
Common Causes:
const state = reactive({ count: 0 })
state.newProp = 'value'
const { count } = reactive({ count: 0 })
let state = reactive({ count: 0 })
state = reactive({ count: 1 })
const count = ref(0)
count = 5
Solutions:
const state = reactive({ count: 0 })
const { count } = toRefs(state)
const count = ref(0)
count.value++
const largeData = shallowRef({ })
import { triggerRef } from 'vue'
triggerRef(myShallowRef)
2. Component Not Updating
Symptoms:
- Props change but component doesn't re-render
- Parent state updates don't propagate to children
- v-for lists don't update correctly
Debugging Steps:
watch(() => props.myProp, (newVal) => {
console.log('Prop changed:', newVal)
}, { immediate: true })
<template>
<!-- WRONG: index as key for dynamic lists -->
<div v-for="(item, index) in items" :key="index">
<div v-for="item in items" :key="item.id">
</template>
// 3. Check for prop mutation (anti-pattern)
// Props should be immutable - emit events instead
emit('update:modelValue', newValue)
3. Pinia Store Issues
Symptoms:
- Store state not updating across components
- Actions not triggering reactivity
- Getters returning stale data
Common Problems:
const store = useMyStore()
const { count } = store
const store = useMyStore()
const { count } = storeToRefs(store)
store.count++
store.increment()
store.$patch({ count: store.count + 1 })
Debugging Pinia:
import { createPinia } from 'pinia'
const pinia = createPinia()
store.$subscribe((mutation, state) => {
console.log('Mutation:', mutation.type, mutation.storeId)
console.log('New state:', state)
})
store.$onAction(({ name, args, after, onError }) => {
console.log(`Action ${name} called with:`, args)
after((result) => console.log(`${name} returned:`, result))
onError((error) => console.error(`${name} failed:`, error))
})
4. Computed Property Caching Issues
Symptoms:
- Computed returns same value despite dependency changes
- Infinite loops in computed properties
- Performance issues with computed
Solutions:
const computed1 = computed(() => {
return someRef.value + nonReactiveValue
})
const bad = computed(() => {
someRef.value = 'changed'
return otherRef.value
})
const myComputed = computed(() => {
console.log('Computed recalculating...')
return expensiveOperation(dep1.value, dep2.value)
})
5. Teleport/Suspense Issues
Teleport Problems:
<template>
<Teleport to="#modal-container">
<Modal />
</Teleport>
<Teleport v-if="isMounted" to="#modal-container">
<Modal />
</Teleport>
</template>
<script setup>
import { ref, onMounted } from 'vue'
const isMounted = ref(false)
onMounted(() => { isMounted.value = true })
</script>
Suspense Problems:
<template>
<Suspense>
<template #default>
<AsyncComponent />
</template>
<template #fallback>
<LoadingSpinner />
</template>
</Suspense>
</template>
<script setup>
import { onErrorCaptured, ref } from 'vue'
const error = ref(null)
onErrorCaptured((err) => {
error.value = err
return false
})
</script>
6. SSR Hydration Mismatches
Symptoms:
- Console warning: "Hydration mismatch"
- Content flickers on page load
- Different content between server and client
Common Causes and Solutions:
const width = window.innerWidth
const width = ref(0)
onMounted(() => {
width.value = window.innerWidth
})
<template>
<ClientOnly>
<BrowserOnlyComponent />
</ClientOnly>
</template>
const isClient = typeof window !== 'undefined'
import { useId } from 'vue'
const id = useId()
Debugging Tools
Vue DevTools
Installation:
Key Features:
- Components Tab: Inspect component hierarchy, props, data, computed
- Pinia Tab: View store state, actions, mutations
- Timeline Tab: Track events, mutations, and performance
- Routes Tab: Debug Vue Router (if using)
DevTools Tips:
$vm
$vm.someMethod()
$vm.someData
$0.__vueParentComponent
Console Debugging
export default {
setup() {
const state = reactive({ count: 0 })
watch(
() => ({ ...state }),
(newState, oldState) => {
console.log('State changed:', { old: oldState, new: newState })
},
{ deep: true }
)
return { state }
}
}
console.table(items.value)
function problematicFunction() {
console.trace('Called from:')
}
console.group('Component Mount')
console.log('Props:', props)
console.log('State:', state)
console.groupEnd()
Debugger Statement
function handleClick() {
debugger
processData()
}
watch(count, (val) => {
if (val > 10) {
debugger
}
})
Vite Dev Server
vite --debug
vite --force
export default defineConfig({
server: {
hmr: {
overlay: true // Show errors as overlay
}
},
build: {
sourcemap: true // Enable source maps
}
})
vue-tsc Type Checking
npx vue-tsc --noEmit
npx vue-tsc --noEmit --watch
npx vue-tsc --noEmit src/components/MyComponent.vue
The Four Phases of Vue Debugging
Phase 1: Identify the Error Type
app.config.errorHandler = (err, instance, info) => {
console.error('Vue Error:', err)
console.log('Component:', instance?.$options?.name || 'Unknown')
console.log('Error Info:', info)
}
app.config.warnHandler = (msg, instance, trace) => {
console.warn('Vue Warning:', msg)
console.log('Trace:', trace)
}
Phase 2: Isolate the Problem
const ErrorBoundary = defineComponent({
setup(_, { slots }) {
const error = ref(null)
onErrorCaptured((err, instance, info) => {
error.value = { err, info }
return false
})
return () => error.value
? h('div', { class: 'error' }, `Error: ${error.value.err.message}`)
: slots.default?.()
}
})
<template>
<ErrorBoundary>
<SuspiciousComponent />
</ErrorBoundary>
</template>
Phase 3: Investigate Root Cause
onBeforeMount(() => console.log('beforeMount'))
onMounted(() => console.log('mounted'))
onBeforeUpdate(() => console.log('beforeUpdate'))
onUpdated(() => console.log('updated'))
onBeforeUnmount(() => console.log('beforeUnmount'))
onUnmounted(() => console.log('unmounted'))
watch(() => props, (newProps) => {
console.log('Props changed:', JSON.stringify(newProps, null, 2))
}, { deep: true, immediate: true })
const emit = defineEmits(['update'])
function emitUpdate(value) {
console.log('Emitting update:', value)
emit('update', value)
}
Phase 4: Apply and Verify Fix
const displayValue = computed(() => {
if (!props.data) {
console.warn('MyComponent: data prop is undefined')
return 'N/A'
}
return props.data.value ?? 'N/A'
})
interface Props {
data?: {
value: string
}
}
const props = withDefaults(defineProps<Props>(), {
data: undefined
})
Quick Reference Commands
Development
npm run dev -- --debug
npm run type-check
npm run lint -- --fix
npm run test:unit
npm run test:unit -- --watch
Build Debugging
npm run build -- --sourcemap
npx vite-bundle-analyzer
npm run preview
Common Fixes
const key = ref(0)
function forceRerender() {
key.value++
}
Object.keys(state).forEach(key => delete state[key])
Object.assign(state, initialState)
store.$reset()
import { nextTick } from 'vue'
await nextTick()
Vue 3 Production Error Codes
Vue 3 uses short error codes in production. Reference:
Error Handling Best Practices
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
app.config.errorHandler = (err, instance, info) => {
if (import.meta.env.DEV) {
console.error('Vue Error:', err)
console.log('Component:', instance)
console.log('Info:', info)
}
if (import.meta.env.PROD) {
errorTracker.captureException(err, {
extra: { component: instance?.$options?.name, info }
})
}
}
app.config.warnHandler = (msg, instance, trace) => {
console.warn('Vue Warning:', msg)
if (import.meta.env.CI) {
throw new Error(`Vue Warning: ${msg}`)
}
}
app.mount('#app')
Resources