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.


Speed up your responsive apps and websites with fully-featured, ready-to-use open-source admin panel templates—free to use and built for efficiency.


About the Author

Subscribe to our newsletter
Get early information about new products, product updates and blog posts.
How to Disable Right Click on a Website Using JavaScript
How to Disable Right Click on a Website Using JavaScript

CSS Selector for Parent Element
CSS Selector for Parent Element

How to show or hide elements in React? A Step-by-Step Guide.
How to show or hide elements in React? A Step-by-Step Guide.

What is globalThis in JavaScript?
What is globalThis in JavaScript?

Answers by CoreUI Core Team