Vue.js: Error Handling

Last updated: 2026-10-20

Error handling is an essential capability for any production-grade application—when something crashes you need to locate the cause fast, degrade gracefully, and tell the user what happened. Vue 3 offers several layers of error handling: errorHandler (global), onErrorCaptured (local), and the errorCaptured hook (component level).

Debugging and performance profiling matter just as much—Vue DevTools is a must-have tool that lets you inspect the component tree, state, events, and performance bottlenecks.

1. What You'll Learn


2. The White-Screen Crash Where "Nobody Can Find the Cause"

(1) Pain Point: One API Error Blanks the Entire Page

Alice's admin dashboard had a serious bug:

JS
// ProductList.vue
const products = ref([])
onMounted(async () => {
  const res = await fetch('/api/products')
  products.value = await res.json()  // ❌ Backend returns non-JSON → throws
})

User experience:

Product manager Charlie:

"Alice, customers are complaining about a blank page. We need to: 1) stop the whole page from crashing, 2) show an error message, 3) report it to our monitoring system."

(2) The Vue Error Boundary Fix

User experience:

(3) Benefits

After adding error handling:


3. 3 Layers of Error Handling

(1) Global Error Handling: app.config.errorHandler

JS
// main.js
const { createApp } = Vue
import App from './App.vue'

const app = createApp(App)

// ✅ Global error handling
app.config.errorHandler = (err, instance, info) => {
  console.error('Global error:', err)
  console.log('Component:', instance)
  console.log('Info:', info)  // 'render' / 'watch' / 'lifecycle hook'
  
  // Report to Sentry
  Sentry.captureException(err)
  
  // Notify the user
  showErrorNotification('Something went wrong')
}

(2) Component-Level Errors: onErrorCaptured

(3) Comparing the Levels

Level API Scope Best for
Global app.config.errorHandler All uncaught errors Required in production
Component onErrorCaptured Errors in child components Local error boundaries
Async try/catch / window.onunhandledrejection Promise errors Manual handling

(4) The 5 Main Error Sources

JS
// 1. Render errors (template syntax mistakes)
app.config.errorHandler = (err, instance, info) => {
  if (info === 'render') console.error('Render error:', err)
}

// 2. Lifecycle hook errors
if (info === 'mounted') console.error('Mounted error:', err)

// 3. watch callback errors
if (info === 'watcher callback') console.error('Watch error:', err)

// 4. Custom directive errors
if (info === 'directive') console.error('Directive error:', err)

// 5. Event handler errors
if (info === 'v-on handler') console.error('Event error:', err)

▶ Example: Vue 3 Global Error Handling (⚠️ requires Vite project main.js)

JS
// main.js
app.config.errorHandler = (err, instance, info) => {
  console.error('[Global]', err, info)
  // Report to Sentry / your own logging service
  // Sentry.captureException(err)
}
▶ Try it Yourself

Output:

TEXT
No UI change.
Every uncaught error inside Vue (render, lifecycle, watcher, directive, or v-on handler) is printed once to the browser console prefixed with [Global], together with the error object and the info string.
TEXT
Console:
[Global] TypeError: Cannot read properties of null (reading 'foo')  render

4. Debugging with Vue DevTools

(1) The 4 Core Panels

Panel What it shows Best for
Components Component tree, state, props, emits Understanding structure, debugging data
Timeline Events, lifecycle, performance Tracing event flow, finding bottlenecks
Pinia/Vuex Store state Debugging global state
Routes Route history Debugging navigation

(2) Using the Components Panel

TEXT
1. Open DevTools (F12 / Cmd+Opt+I)
2. Switch to the Vue tab
3. Click a component in the tree
4. The right panel shows:
   - State (reactive data)
   - Props
   - Emits
   - Slots
   - Lifecycle

(3) 5 DevTools Techniques

JS
// 1. Edit state live (dev mode)
// Edit directly in DevTools and watch the component react immediately

// 2. Time travel (Pinia)
// Switch to the Pinia panel → Base → replay each state change step by step

// 3. Performance marking
// import { markRaw } from 'vue'
// Mark large data objects with markRaw so DevTools does not traverse them deeply

// 4. Component highlight
// DevTools → the "eye" icon in the top right → hover to highlight components

// 5. Route navigation
// DevTools → Routing tab → inspect the path + params of every navigation

(4) Installing Vue DevTools

BASH
# Chrome extension
# https://chromewebstore.google.com/detail/vuejs-devtools/odjccnclnlddjlajjphfdmhnlhaglgki

# Firefox extension
# https://addons.mozilla.org/en-US/firefox/addon/vue-js-devtools/

# Or the standalone app (recommended)
# https://devtools.vuejs.org/

5. Performance Profiling

(1) The Vue 3 Performance API

JS
const { onMounted, onUnmounted } = Vue

onMounted(() => {
  performance.mark('app-start')
  
  // Your code
  loadData()
  
  performance.mark('app-end')
  performance.measure('app-load', 'app-start', 'app-end')
  
  const measure = performance.getEntriesByName('app-load')[0]
  console.log(`App loaded in ${measure.duration}ms`)
})

(2) 5 Performance Optimization Techniques

JS
// 1. v-once: render only once
<h1 v-once>{{ title }}</h1>

// 2. v-memo: cache a subtree
<div v-memo="[item.id, item.updatedAt]">
  <!-- Re-renders only when id or updatedAt changes -->
</div>

// 3. shallowRef: no deep reactivity for large data
const bigList = shallowRef([...10000 items])

// 4. markRaw: keep third-party libraries out of reactivity
const map = markRaw(new Map())

// 5. Lazy loading: routes / components
const Heavy = defineAsyncComponent(() => import('./Heavy.vue'))

(3) The 5 Key Performance Metrics

Metric Target How to measure
FCP (First Contentful Paint) < 1.8s Lighthouse
LCP (Largest Contentful Paint) < 2.5s Lighthouse
TTI (Time to Interactive) < 3.8s Lighthouse
TBT (Total Blocking Time) < 200ms Lighthouse
CLS (Cumulative Layout Shift) < 0.1 Lighthouse

▶ Example: 5 Performance Optimizations (quick reference)

JS
// 1. v-once (render only once)
<h1 v-once>{{ title }}</h1>

// 2. v-memo (cache)
<div v-memo="[item.id]">...</div>

// 3. shallowRef (shallow reactivity)
const list = shallowRef([...])

// 4. markRaw (no reactivity)
const map = markRaw(new Map())

// 5. Async component (lazy loading)
const Heavy = defineAsyncComponent(() => import('./Heavy.vue'))
▶ Try it Yourself

Output:

TEXT
These are snippets rather than a runnable page.
Once applied: the v-once heading renders a single time and never updates again;
the v-memo block re-renders only when item.id changes;
shallowRef stops Vue from deeply tracking the 10,000-item array, so mutations inside it are far cheaper;
markRaw keeps a Map instance outside the reactivity system;
and the async component emits a separate chunk that is fetched only when first rendered.
TEXT
Effect on rendering:
v-once        → 1 render total, later updates skipped
v-memo        → re-render only when item.id changes
shallowRef    → deep values are not tracked (assign .value to trigger)
markRaw       → object never becomes reactive
async import  → separate chunk loaded on demand

6. Sentry Integration

(1) Installation

BASH
npm install @sentry/vue @sentry/tracing

(2) main.js Configuration

JS
const { createApp } = Vue
import * as Sentry from '@sentry/vue'
import { Integrations } from '@sentry/tracing'
import App from './App.vue'

const app = createApp(App)

// 1. Initialize Sentry (the app instance must exist first)
Sentry.init({
  app,
  dsn: 'https://your-dsn@sentry.io/123',
  integrations: [
    new Integrations.BrowserTracing()
  ],
  tracesSampleRate: 1.0,
  // Lower the sample rate in production
  // tracesSampleRate: process.env.NODE_ENV === 'production' ? 0.1 : 1.0
  
  // Vue-specific options
  logErrors: true,
  release: '1.0.0',
  environment: process.env.NODE_ENV
})

// 2. Report global errors
app.config.errorHandler = (err, instance, info) => {
  Sentry.captureException(err, {
    extra: {
      component: instance?.$options.name,
      info
    }
  })
}

(3) Source Map Configuration

JS
// vite.config.js
export default {
  build: {
    sourcemap: true  // Generate source maps in production too
  }
}

// Upload source maps to Sentry
// @sentry/cli releases -o your-org -p your-project files upload-sourcemaps ./dist

(4) 5 Sentry Features

Feature What it does
Error monitoring Captures every error automatically, viewable in a dashboard
Performance tracing Locates slow requests and slow DB queries
Session replay Records what the user did to see the error in context
Release health Compares the error rate of each release
Alerts Slack / Email notifications

7. Putting It Together: Error Boundary in Action

▶ Example: 3 Layers of Error Handling (ErrorBoundary demonstrable via CDN)

▶ Try it Yourself

Output:

TEXT
The page renders a toggle button and two independent ErrorBoundary regions.
Initially both show their normal content. Clicking the button mounts the buggy child;
its render error is caught by the enclosing boundary, which replaces only that region with a red "⚠️ Error caught: ..." box and a retry button, while the other boundary keeps rendering normally.
Clicking the button again restores the content.
TEXT
┌───────────────────────────────────────────────┐
│ [Trigger child error]                         │
│                                               │
│ Outside ErrorBoundary - works normally        │
│                                               │
│ ┌───────────────────────────────────────────┐ │
│ │ ⚠️ Error caught: Cannot read properties   │ │
│ │    of null (reading 'foo')     [Retry]    │ │
│ └───────────────────────────────────────────┘ │
└───────────────────────────────────────────────┘

The rest of the page keeps working; only the faulty
boundary shows the error UI.

(1) 📊 The 5 Main Error Sources (info field)

info value Meaning Example
'render' Render error Template references an undefined variable
'mounted' / 'updated' Lifecycle error Accessing the DOM inside onMounted fails
'watcher callback' Watch error A watch callback throws
'directive' Directive error A custom directive throws in mounted
'v-on handler' Event error An @click handler throws

(2) 📊 Quick Reference for 5 Common Mistakes

Mistake Symptom Fix
White-screen crash One component error kills everything Wrap it in an ErrorBoundary
Swallowed error Child component fails silently Don't forget return false in onErrorCaptured
Missing Source Map Production errors can't be located vite build with sourcemap: true
Poor performance A 1000-item list stutters v-memo + shallowRef
Memory leak Timers keep running after unmount Clean up in onUnmounted

(3) 📊 5 Debugging Scenarios

Scenario Tool
Unexpected component state Vue DevTools → State
Event doesn't fire Vue DevTools → Timeline
Wrong route navigation DevTools → Routing
Production crash Sentry dashboard
Slow performance Lighthouse + DevTools Performance

❓ FAQ

Q Can errorHandler catch asynchronous errors?
A No. errorHandler only catches synchronous errors inside Vue. Async errors (Promise / setTimeout) need try/catch or window.addEventListener('unhandledrejection').
Q Must onErrorCaptured return false?
A No. Returning false stops the error from propagating upwards. Returning true (or nothing) lets it continue up to the global errorHandler.
Q Can Vue DevTools be used in production?
A Yes. The production build enables DevTools automatically (without affecting performance). It is still recommended to disable them in production with Vue.config.devtools = false (to avoid exposing internal structure).
Q How is Sentry priced?
A The free tier includes 5,000 events and 10,000 performance transactions per month, which is enough for a mid-sized project. Beyond that you pay per usage.
Q How do v-memo and v-for work together?
A With <div v-for="item in items" :key="item.id" v-memo="[item.id, item.updatedAt]">, this div only re-renders when id or updatedAt changes.
Q How do I debug an SSR app?
A Use the VS Code debugger with Node.js inspect mode, or let Sentry capture server-side errors, or use the Nitro debugging tools in Nuxt 3.
Q How does onErrorCaptured differ between Vue 3 and Vue 2?
A Vue 2 uses the errorCaptured hook; Vue 3 uses the onErrorCaptured Composition API. Vue 3 also adds the global app.config.errorHandler.

📖 Summary


📝 Exercises

  1. Basic (difficulty ⭐) Build an ErrorBoundary component:

    • onErrorCaptured catches errors from child components
    • Show a friendly error page
    • Provide a "Retry" button
  2. Intermediate (difficulty ⭐⭐) Build a complete error handling system:

    • Global errorHandler reporting to the console
    • Component-level ErrorBoundary
    • try/catch for async errors
    • Classification of the 5 error sources
  3. Challenge (difficulty ⭐⭐⭐) Build a complete "production-grade" debugging and error monitoring setup:

    1. ErrorBoundary components in 3 different places
    2. Sentry integration (production)
    3. Source Map configuration
    4. The 5 Vue DevTools debugging techniques
    5. The 5 performance optimizations (v-memo / shallowRef / markRaw / lazy loading / v-once)
    6. Monitoring of the 5 performance metrics (Lighthouse integration)
Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏