How to manage errors in Vue apps
Managing errors in Vue applications ensures graceful handling of exceptions and provides better user experience during failures. With over 12 years of Vue.js experience since 2014 and as the creator of CoreUI, I’ve implemented error handling in production Vue applications. Vue provides global error handlers, lifecycle hooks, and patterns for catching and managing errors at different levels. This approach prevents application crashes and helps track errors for debugging and monitoring.
Use Vue global error handlers and try-catch blocks to manage errors and prevent application crashes.
Global error handler:
// main.js
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
app.config.errorHandler = (err, instance, info) => {
console.error('Global error:', err)
console.error('Component:', instance)
console.error('Error info:', info)
// Send to error tracking service
// trackError(err, { component: instance?.$options.name, info })
}
app.config.warnHandler = (msg, instance, trace) => {
console.warn('Warning:', msg)
console.warn('Trace:', trace)
}
app.mount('#app')
Error boundary component:
<!-- ErrorBoundary.vue -->
<template>
<div>
<div v-if='hasError' class='error-boundary'>
<h2>Something went wrong</h2>
<p>{{ errorMessage }}</p>
<button @click='resetError'>Try again</button>
</div>
<slot v-else></slot>
</div>
</template>
<script setup>
import { ref, onErrorCaptured } from 'vue'
const hasError = ref(false)
const errorMessage = ref('')
onErrorCaptured((err, instance, info) => {
hasError.value = true
errorMessage.value = err.message
console.error('Error captured:', err, info)
// Return false to stop propagation
return false
})
const resetError = () => {
hasError.value = false
errorMessage.value = ''
}
</script>
<style scoped>
.error-boundary {
padding: 20px;
border: 2px solid #dc3545;
border-radius: 8px;
background: #f8d7da;
color: #721c24;
}
</style>
Async error handling:
<template>
<div>
<div v-if='loading'>Loading...</div>
<div v-else-if='error' class='error'>
<p>Error: {{ error }}</p>
<button @click='retry'>Retry</button>
</div>
<div v-else>
<div v-for='user in users' :key='user.id'>
{{ user.name }}
</div>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
const users = ref([])
const loading = ref(false)
const error = ref(null)
const fetchUsers = async () => {
loading.value = true
error.value = null
try {
const response = await fetch('/api/users')
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
users.value = await response.json()
} catch (err) {
error.value = err.message
console.error('Failed to fetch users:', err)
} finally {
loading.value = false
}
}
const retry = () => {
fetchUsers()
}
fetchUsers()
</script>
Composable for error handling:
// useErrorHandler.js
import { ref } from 'vue'
export function useErrorHandler() {
const error = ref(null)
const isError = ref(false)
const handleError = (err) => {
error.value = err.message || 'An error occurred'
isError.value = true
console.error('Error handled:', err)
}
const clearError = () => {
error.value = null
isError.value = false
}
const withErrorHandling = async (fn) => {
try {
clearError()
return await fn()
} catch (err) {
handleError(err)
throw err
}
}
return {
error,
isError,
handleError,
clearError,
withErrorHandling
}
}
// Usage in component
<script setup>
import { useErrorHandler } from './useErrorHandler'
const { error, isError, withErrorHandling } = useErrorHandler()
const fetchData = async () => {
await withErrorHandling(async () => {
const response = await fetch('/api/data')
const data = await response.json()
return data
})
}
</script>
Axios interceptor:
// axiosConfig.js
import axios from 'axios'
import { createApp } from 'vue'
const api = axios.create({
baseURL: '/api'
})
api.interceptors.response.use(
(response) => response,
(error) => {
const message = error.response?.data?.message || error.message
// Global notification
console.error('API Error:', message)
// Track error
// trackError(error)
return Promise.reject(error)
}
)
export default api
Form validation errors:
<template>
<form @submit.prevent='handleSubmit'>
<div>
<input v-model='form.email' type='email' />
<span v-if='errors.email' class='error'>{{ errors.email }}</span>
</div>
<div>
<input v-model='form.password' type='password' />
<span v-if='errors.password' class='error'>{{ errors.password }}</span>
</div>
<div v-if='errors.general' class='error'>{{ errors.general }}</div>
<button type='submit'>Submit</button>
</form>
</template>
<script setup>
import { ref } from 'vue'
const form = ref({
email: '',
password: ''
})
const errors = ref({
email: '',
password: '',
general: ''
})
const validateForm = () => {
errors.value = {}
let isValid = true
if (!form.value.email) {
errors.value.email = 'Email is required'
isValid = false
}
if (!form.value.password) {
errors.value.password = 'Password is required'
isValid = false
}
return isValid
}
const handleSubmit = async () => {
if (!validateForm()) return
try {
const response = await fetch('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(form.value)
})
if (!response.ok) {
const data = await response.json()
errors.value.general = data.message
return
}
// Success
} catch (err) {
errors.value.general = 'Network error. Please try again.'
}
}
</script>
Best Practice Note
Use global error handler to catch unhandled errors and send to monitoring services. Implement error boundaries around critical sections using onErrorCaptured. Always handle async errors with try-catch blocks. Show user-friendly error messages instead of raw errors. Provide retry mechanisms for recoverable errors. Log errors with context for debugging. This is how we manage errors in CoreUI Vue applications—comprehensive error handling at multiple levels ensuring graceful degradation and excellent user experience when things go wrong.



