Skip to content
← All questions
🔥 Advanced

How would you implement global error handling in a Vue app?

🛡️ Error Handling🏗️ Architecture

Vue provides multiple layers for catching errors: app.config.errorHandler for uncaught errors globally, onErrorCaptured for errors in a component subtree, and try/catch for async operations. A production app should combine all three, plus a user-facing error UI.

Flowchart showing three-layer error handling: try/catch, onErrorCaptured boundary, app.config.errorHandler

app.config.errorHandler (global catch-all) ​

This is the last line of defense. It catches any unhandled error from components, watchers, lifecycle hooks, and event handlers:

ts
// main.ts
const app = createApp(App)

app.config.errorHandler = (err, instance, info) => {
  console.error('Unhandled error:', err)
  console.error('Component:', instance?.$options?.name || 'unknown')
  console.error('Hook:', info)

  // Send to error tracking service
  reportToSentry(err, { component: instance?.$options?.name, info })
}
ParameterWhat it contains
errThe Error object
instanceThe component instance that threw (or null)
infoWhere the error occurred: 'setup function', 'render function', 'watcher callback', etc. In production builds, info is a shortened numeric code instead of the full string. See the Error Reference for mappings.

onErrorCaptured (component-level boundary) ​

onErrorCaptured catches errors from any descendant component. It works like an error boundary: you can handle the error locally and prevent it from propagating up.

vue
<!-- components/ErrorBoundary.vue -->
<script setup lang="ts">
const error = ref<Error | null>(null)

onErrorCaptured((err) => {
  error.value = err
  return false // stop propagation — don't reach app.config.errorHandler
})

function retry() {
  error.value = null
}
</script>

<template>
  <div v-if="error" class="error-state">
    <h3>Something went wrong</h3>
    <p>{{ error.message }}</p>
    <button @click="retry">Try again</button>
  </div>
  <slot v-else />
</template>
Open in Vue Playground

Wrap sections of your app that can fail:

vue
<template>
  <AppHeader />
  <ErrorBoundary>
    <RouterView />
  </ErrorBoundary>
  <AppFooter />
</template>
Open in Vue Playground

If a page crashes, the header and footer stay visible. The user sees an error message with a retry button instead of a blank screen.

Return value of onErrorCaptured ​

ReturnEffect
falseError is captured, stops propagating
true or nothingError continues to parent and eventually to app.config.errorHandler

Async error handling ​

app.config.errorHandler catches errors in async lifecycle hooks and watchers. But $fetch, fetch, or any promise in an event handler needs explicit try/catch:

vue
<script setup>
const error = ref<string | null>(null)
const isLoading = ref(false)

async function submitForm(data: FormData) {
  error.value = null
  isLoading.value = true
  try {
    await $fetch('/api/submit', { method: 'POST', body: data })
    navigateTo('/success')
  } catch (e) {
    error.value = e instanceof Error ? e.message : 'Something went wrong'
  } finally {
    isLoading.value = false
  }
}
</script>

<template>
  <div v-if="error" class="alert-error">{{ error }}</div>
  <form @submit.prevent="submitForm">...</form>
</template>
Open in Vue Playground

Composable for async operations ​

Extract the try/catch pattern into a reusable composable:

ts
// composables/useAsyncAction.ts
export function useAsyncAction<T>(action: () => Promise<T>) {
  const isLoading = ref(false)
  const error = ref<string | null>(null)

  async function execute() {
    isLoading.value = true
    error.value = null
    try {
      const result = await action()
      return result
    } catch (e) {
      error.value = e instanceof Error ? e.message : 'Unexpected error'
      return null
    } finally {
      isLoading.value = false
    }
  }

  return { execute, isLoading, error }
}
vue
<script setup>
const {
  execute: submit,
  isLoading,
  error
} = useAsyncAction(() =>
  $fetch('/api/submit', { method: 'POST', body: formData })
)
</script>
Open in Vue Playground

Nuxt error handling ​

Nuxt adds framework-level error handling on top of Vue's:

error.vue catches fatal errors and renders a full-page error screen:

vue
<!-- error.vue -->
<script setup lang="ts">
const props = defineProps<{ error: { statusCode: number; message: string } }>()

function goHome() {
  clearError({ redirect: '/' })
}
</script>

<template>
  <div class="error-page">
    <h1>{{ error.statusCode }}</h1>
    <p>{{ error.message }}</p>
    <button @click="goHome">Go home</button>
  </div>
</template>
Open in Vue Playground

showError / createError for explicit error throwing:

ts
// In a page or middleware
throw createError({ statusCode: 404, statusMessage: 'Page not found' })

NuxtErrorBoundary for scoped error catching:

vue
<template>
  <NuxtErrorBoundary>
    <SomeRiskyComponent />
    <template #error="{ error, clearError }">
      <p>{{ error.message }}</p>
      <button @click="clearError">Retry</button>
    </template>
  </NuxtErrorBoundary>
</template>
Open in Vue Playground

Error handling layers ​

Try/catch in event handlers (local, explicit)
        ↓ uncaught
onErrorCaptured in ErrorBoundary (component subtree)
        ↓ propagates if not returning false
app.config.errorHandler (global catch-all)
        ↓ in Nuxt
error.vue (fatal page-level errors)

See also: How do you debug SSR requests? · What causes SSR hydration mismatches? · How does the Vue plugin system work?

References ​

Released under the MIT License.