Nuxt 错误处理与异常捕获完全指南

HTMLPAGE 团队
20分钟 分钟阅读

全面解析 Nuxt 3 的错误处理机制,涵盖客户端与服务端错误捕获、自定义错误页面、API 错误处理、全局错误边界、错误日志上报,以及生产环境的错误监控最佳实践。

#Nuxt #错误处理 #异常捕获 #Error Boundary #错误监控

Nuxt 错误处理与异常捕获完全指南

在现代 Web 应用中,错误是不可避免的——API 请求可能失败、用户输入可能无效、第三方服务可能宕机。如何优雅地处理这些错误,直接决定了应用的健壮性和用户体验。

Nuxt 3 作为全栈框架,需要同时处理客户端错误服务端错误,情况更加复杂。本文将系统讲解 Nuxt 的错误处理机制,从基础概念到生产级实践。


错误的分类与特点

在深入实现之前,先理解 Nuxt 中错误的不同类型。

按运行环境分类

类型发生环境示例
服务端错误Node.js 服务器数据库连接失败、SSR 渲染异常
客户端错误浏览器事件处理函数异常、组件渲染错误
混合错误两端都可能API 调用失败、数据格式错误

按严重程度分类

级别描述处理策略
致命错误应用无法继续运行显示错误页面
可恢复错误局部功能受损降级处理、重试
可忽略错误不影响核心功能静默上报

Nuxt 错误的特殊性

由于 Nuxt 支持 SSR,同一段代码可能在服务端和客户端都执行。这带来了独特的挑战:

// 这段代码在 SSR 时会报错
const width = window.innerWidth  // ❌ window 在服务端不存在

// 正确的做法
const width = process.client ? window.innerWidth : 0

error.vue:全局错误页面

Nuxt 提供了一个约定:当发生致命错误时,会渲染项目根目录的 error.vue 组件。

基础错误页面

<!-- error.vue -->
<script setup lang="ts">
import type { NuxtError } from '#app'

const props = defineProps<{
  error: NuxtError
}>()

// 清除错误并导航到首页
const handleError = () => {
  clearError({ redirect: '/' })
}
</script>

<template>
  <div class="error-page">
    <div class="error-content">
      <h1 class="error-code">{{ error.statusCode }}</h1>
      <h2 class="error-title">{{ error.statusMessage || '出错了' }}</h2>
      <p class="error-message">{{ error.message }}</p>
      
      <div class="error-actions">
        <button @click="handleError" class="btn-primary">
          返回首页
        </button>
        <button @click="$router.back()" class="btn-secondary">
          返回上页
        </button>
      </div>
    </div>
  </div>
</template>

<style scoped>
.error-page {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}

.error-content {
  text-align: center;
  color: white;
  padding: 2rem;
}

.error-code {
  font-size: 8rem;
  font-weight: 700;
  margin: 0;
  text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
}

.error-title {
  font-size: 2rem;
  margin: 1rem 0;
}

.error-message {
  font-size: 1.1rem;
  opacity: 0.9;
  max-width: 500px;
  margin: 0 auto 2rem;
}

.error-actions {
  display: flex;
  gap: 1rem;
  justify-content: center;
}

.btn-primary, .btn-secondary {
  padding: 0.75rem 1.5rem;
  border-radius: 8px;
  font-size: 1rem;
  cursor: pointer;
  transition: transform 0.2s;
}

.btn-primary {
  background: white;
  color: #667eea;
  border: none;
}

.btn-secondary {
  background: transparent;
  color: white;
  border: 2px solid white;
}

.btn-primary:hover, .btn-secondary:hover {
  transform: translateY(-2px);
}
</style>

差异化错误页面

根据错误类型显示不同内容:

<!-- error.vue -->
<script setup lang="ts">
import type { NuxtError } from '#app'

const props = defineProps<{
  error: NuxtError
}>()

// 错误类型配置
const errorConfig = computed(() => {
  const code = props.error.statusCode
  
  const configs: Record<number, { 
    title: string
    message: string
    icon: string 
  }> = {
    404: {
      title: '页面不存在',
      message: '您访问的页面已被移除或从未存在',
      icon: '🔍'
    },
    403: {
      title: '访问被拒绝',
      message: '您没有权限访问此资源',
      icon: '🔒'
    },
    500: {
      title: '服务器错误',
      message: '服务器遇到了问题,请稍后重试',
      icon: '⚙️'
    },
    503: {
      title: '服务不可用',
      message: '服务器正在维护,请稍后访问',
      icon: '🔧'
    }
  }
  
  return configs[code] || {
    title: '出错了',
    message: props.error.message || '发生了未知错误',
    icon: '❌'
  }
})

// 开发环境显示详细堆栈
const isDev = process.env.NODE_ENV === 'development'

const handleError = () => {
  clearError({ redirect: '/' })
}
</script>

<template>
  <div class="error-page">
    <div class="error-container">
      <div class="error-icon">{{ errorConfig.icon }}</div>
      <h1 class="error-code">{{ error.statusCode }}</h1>
      <h2 class="error-title">{{ errorConfig.title }}</h2>
      <p class="error-description">{{ errorConfig.message }}</p>
      
      <!-- 开发环境显示堆栈 -->
      <details v-if="isDev && error.stack" class="error-stack">
        <summary>查看错误详情</summary>
        <pre>{{ error.stack }}</pre>
      </details>
      
      <div class="error-actions">
        <button @click="handleError">返回首页</button>
        <button @click="() => reloadNuxtApp()">刷新页面</button>
      </div>
    </div>
  </div>
</template>

使用 createError 主动抛出错误

Nuxt 提供 createError 函数来创建格式化的错误对象。

在页面/组件中抛出

// pages/products/[id].vue
<script setup lang="ts">
const route = useRoute()

const { data: product, error } = await useFetch(`/api/products/${route.params.id}`)

// 产品不存在时抛出 404
if (!product.value) {
  throw createError({
    statusCode: 404,
    statusMessage: 'Product Not Found',
    message: `找不到 ID 为 ${route.params.id} 的产品`,
    fatal: true  // 致命错误,显示错误页面
  })
}
</script>

在 API 路由中抛出

// server/api/products/[id].ts
export default defineEventHandler(async (event) => {
  const id = getRouterParam(event, 'id')
  
  const product = await prisma.product.findUnique({
    where: { id }
  })
  
  if (!product) {
    throw createError({
      statusCode: 404,
      statusMessage: 'Not Found',
      message: `产品 ${id} 不存在`
    })
  }
  
  // 权限检查
  const user = event.context.user
  if (product.isPrivate && product.ownerId !== user?.id) {
    throw createError({
      statusCode: 403,
      statusMessage: 'Forbidden',
      message: '您无权访问此产品'
    })
  }
  
  return product
})

非致命错误(局部处理)

// 非致命错误不会触发错误页面
const error = createError({
  statusCode: 400,
  message: '表单验证失败',
  fatal: false  // 默认值
})

// 可以在组件中局部处理
showError(error)

// 稍后清除
clearError()

showError 与 clearError

这两个函数用于控制错误的显示和清除。

showError:手动触发错误

// 在任何地方触发错误页面
showError({
  statusCode: 500,
  statusMessage: 'Server Error',
  message: '服务暂时不可用'
})

// 等同于
throw createError({ ... })

clearError:清除错误状态

// 清除错误并保持当前页面
clearError()

// 清除错误并重定向
clearError({ redirect: '/' })

// 清除错误并跳转到指定路由
clearError({ redirect: '/login' })

错误状态访问

// 在任意组件中访问当前错误
const error = useError()

// 响应式
watch(error, (newError) => {
  if (newError) {
    // 上报错误
    reportError(newError)
  }
})

onErrorCaptured:组件错误边界

Vue 3 的 onErrorCaptured 生命周期可以捕获子组件的错误,实现局部错误边界。

基础错误边界组件

<!-- components/ErrorBoundary.vue -->
<script setup lang="ts">
import { ref, onErrorCaptured } from 'vue'

const props = withDefaults(defineProps<{
  fallback?: string
  onError?: (error: Error) => void
}>(), {
  fallback: '组件加载出错'
})

const error = ref<Error | null>(null)
const errorInfo = ref<string>('')

onErrorCaptured((err, instance, info) => {
  error.value = err
  errorInfo.value = info
  
  // 调用外部错误处理
  props.onError?.(err)
  
  // 返回 false 阻止错误继续向上传播
  // 返回 true 或不返回,错误继续传播
  return false
})

const retry = () => {
  error.value = null
  errorInfo.value = ''
}
</script>

<template>
  <div v-if="error" class="error-boundary">
    <div class="error-fallback">
      <p>{{ fallback }}</p>
      <button @click="retry">重试</button>
      
      <details v-if="$config.public.isDev">
        <summary>错误详情</summary>
        <pre>{{ error.message }}</pre>
        <pre>{{ errorInfo }}</pre>
      </details>
    </div>
  </div>
  <slot v-else />
</template>

<style scoped>
.error-boundary {
  padding: 1rem;
  background: #fff3cd;
  border: 1px solid #ffc107;
  border-radius: 8px;
}

.error-fallback {
  text-align: center;
}

.error-fallback button {
  margin-top: 0.5rem;
  padding: 0.5rem 1rem;
  cursor: pointer;
}

details {
  margin-top: 1rem;
  text-align: left;
}

pre {
  background: #f8f9fa;
  padding: 0.5rem;
  overflow-x: auto;
  font-size: 0.875rem;
}
</style>

使用错误边界

<!-- pages/dashboard.vue -->
<template>
  <div class="dashboard">
    <h1>控制台</h1>
    
    <!-- 图表组件出错不影响其他部分 -->
    <ErrorBoundary fallback="图表加载失败" @error="handleChartError">
      <DashboardChart :data="chartData" />
    </ErrorBoundary>
    
    <!-- 数据表格 -->
    <ErrorBoundary fallback="数据加载失败">
      <DataTable :items="tableData" />
    </ErrorBoundary>
    
    <!-- 侧边栏 -->
    <ErrorBoundary>
      <Sidebar />
    </ErrorBoundary>
  </div>
</template>

<script setup>
const handleChartError = (error) => {
  // 上报图表错误
  console.error('Chart error:', error)
}
</script>

高级错误边界(支持重试)

<!-- components/ErrorBoundaryAdvanced.vue -->
<script setup lang="ts">
import { ref, onErrorCaptured, provide } from 'vue'

const props = withDefaults(defineProps<{
  maxRetries?: number
  retryDelay?: number
}>(), {
  maxRetries: 3,
  retryDelay: 1000
})

const emit = defineEmits<{
  error: [error: Error, info: string]
  retry: [attempt: number]
  maxRetriesReached: []
}>()

const error = ref<Error | null>(null)
const retryCount = ref(0)
const isRetrying = ref(false)
const componentKey = ref(0)

onErrorCaptured((err, instance, info) => {
  error.value = err
  emit('error', err, info)
  
  // 自动重试逻辑
  if (retryCount.value < props.maxRetries) {
    autoRetry()
  } else {
    emit('maxRetriesReached')
  }
  
  return false
})

const autoRetry = async () => {
  isRetrying.value = true
  retryCount.value++
  
  await new Promise(resolve => setTimeout(resolve, props.retryDelay))
  
  emit('retry', retryCount.value)
  error.value = null
  componentKey.value++
  isRetrying.value = false
}

const manualRetry = () => {
  retryCount.value = 0
  error.value = null
  componentKey.value++
}

// 提供给子组件的重试方法
provide('errorBoundaryRetry', manualRetry)
</script>

<template>
  <div v-if="error && !isRetrying" class="error-state">
    <slot name="error" :error="error" :retry="manualRetry" :retryCount="retryCount">
      <div class="default-error">
        <p>加载出错</p>
        <p v-if="retryCount >= maxRetries">已达最大重试次数</p>
        <button @click="manualRetry">手动重试</button>
      </div>
    </slot>
  </div>
  
  <div v-else-if="isRetrying" class="retrying-state">
    <slot name="loading">
      <p>重试中... ({{ retryCount }}/{{ maxRetries }})</p>
    </slot>
  </div>
  
  <slot v-else :key="componentKey" />
</template>

API 请求错误处理

在 Nuxt 中,数据获取是最常见的错误来源。

useFetch 错误处理

// 基础错误处理
const { data, error, pending, refresh } = await useFetch('/api/products')

// 监听错误
watch(error, (newError) => {
  if (newError) {
    // 显示 toast 通知
    toast.error(newError.message)
  }
})

封装请求工具

// composables/useApi.ts
import type { NuxtError } from '#app'

interface ApiOptions {
  showError?: boolean      // 是否显示错误提示
  throwOnError?: boolean   // 是否抛出错误
  retries?: number         // 重试次数
  retryDelay?: number      // 重试间隔
}

interface ApiResult<T> {
  data: Ref<T | null>
  error: Ref<NuxtError | null>
  pending: Ref<boolean>
  refresh: () => Promise<void>
}

export function useApi<T>(
  url: string | (() => string),
  options: ApiOptions = {}
): Promise<ApiResult<T>> {
  const {
    showError = true,
    throwOnError = false,
    retries = 0,
    retryDelay = 1000
  } = options
  
  const retryCount = ref(0)
  
  const fetchData = async (): Promise<ApiResult<T>> => {
    const result = await useFetch<T>(url, {
      onResponseError({ response }) {
        const errorMessage = response._data?.message || response.statusText
        
        if (showError) {
          // 使用全局 toast 或通知系统
          useToast().error(errorMessage)
        }
        
        // 记录错误日志
        console.error(`API Error [${response.status}]:`, errorMessage)
      }
    })
    
    // 重试逻辑
    if (result.error.value && retryCount.value < retries) {
      retryCount.value++
      await new Promise(resolve => setTimeout(resolve, retryDelay))
      return fetchData()
    }
    
    // 抛出错误
    if (throwOnError && result.error.value) {
      throw createError({
        statusCode: result.error.value.statusCode || 500,
        message: result.error.value.message
      })
    }
    
    return result as ApiResult<T>
  }
  
  return fetchData()
}

使用封装后的 API

<script setup lang="ts">
// 自动显示错误提示
const { data: products, pending, refresh } = await useApi<Product[]>('/api/products')

// 静默错误(不显示提示)
const { data: stats } = await useApi('/api/stats', { 
  showError: false 
})

// 带重试
const { data: critical } = await useApi('/api/critical-data', {
  retries: 3,
  retryDelay: 2000
})
</script>

API 路由统一错误格式

// server/utils/response.ts
export interface ApiResponse<T = any> {
  success: boolean
  data?: T
  error?: {
    code: string
    message: string
    details?: any
  }
}

export function successResponse<T>(data: T): ApiResponse<T> {
  return {
    success: true,
    data
  }
}

export function errorResponse(
  code: string, 
  message: string, 
  details?: any
): ApiResponse {
  return {
    success: false,
    error: { code, message, details }
  }
}
// server/api/products/[id].ts
import { successResponse, errorResponse } from '~/server/utils/response'

export default defineEventHandler(async (event) => {
  try {
    const id = getRouterParam(event, 'id')
    
    const product = await prisma.product.findUnique({
      where: { id }
    })
    
    if (!product) {
      setResponseStatus(event, 404)
      return errorResponse('PRODUCT_NOT_FOUND', `产品 ${id} 不存在`)
    }
    
    return successResponse(product)
    
  } catch (error) {
    console.error('Product fetch error:', error)
    
    setResponseStatus(event, 500)
    return errorResponse('INTERNAL_ERROR', '服务器内部错误')
  }
})

全局错误处理插件

通过 Nuxt 插件可以实现全局错误监听。

Vue 错误处理器

// plugins/error-handler.ts
export default defineNuxtPlugin((nuxtApp) => {
  // Vue 全局错误处理
  nuxtApp.vueApp.config.errorHandler = (error, instance, info) => {
    console.error('Vue Error:', error)
    console.error('Component:', instance)
    console.error('Info:', info)
    
    // 上报到错误监控服务
    if (process.client) {
      reportToSentry(error, { component: instance?.$options?.name, info })
    }
  }
  
  // Vue 警告处理(仅开发环境)
  nuxtApp.vueApp.config.warnHandler = (msg, instance, trace) => {
    console.warn('Vue Warning:', msg)
    console.warn('Trace:', trace)
  }
  
  // Nuxt 生命周期钩子错误
  nuxtApp.hook('app:error', (error) => {
    console.error('Nuxt App Error:', error)
  })
  
  // 页面渲染错误
  nuxtApp.hook('vue:error', (error, instance, info) => {
    console.error('Vue Error Hook:', error)
  })
})

function reportToSentry(error: any, context: any) {
  // 集成 Sentry 或其他错误监控服务
  if (window.Sentry) {
    window.Sentry.captureException(error, {
      extra: context
    })
  }
}

未捕获异常处理

// plugins/unhandled-errors.client.ts
export default defineNuxtPlugin(() => {
  // 未捕获的 Promise 异常
  window.addEventListener('unhandledrejection', (event) => {
    console.error('Unhandled Promise Rejection:', event.reason)
    
    // 可选:阻止默认的控制台错误
    // event.preventDefault()
    
    // 上报错误
    reportError({
      type: 'unhandledrejection',
      error: event.reason,
      timestamp: Date.now()
    })
  })
  
  // 未捕获的同步异常
  window.addEventListener('error', (event) => {
    console.error('Uncaught Error:', event.error)
    
    reportError({
      type: 'error',
      error: event.error,
      message: event.message,
      filename: event.filename,
      lineno: event.lineno,
      colno: event.colno,
      timestamp: Date.now()
    })
  })
})

function reportError(errorData: any) {
  // 发送到错误收集端点
  navigator.sendBeacon('/api/errors', JSON.stringify(errorData))
}

服务端错误处理

Server Middleware 错误处理

// server/middleware/error-handler.ts
export default defineEventHandler((event) => {
  // 这个 middleware 会在所有请求之前运行
  // 可以用于设置错误处理上下文
  
  event.context.requestId = generateRequestId()
  event.context.requestStart = Date.now()
})
// server/plugins/error-logger.ts
export default defineNitroPlugin((nitroApp) => {
  nitroApp.hooks.hook('error', async (error, { event }) => {
    // 记录服务端错误
    console.error('Server Error:', {
      requestId: event?.context?.requestId,
      path: event?.path,
      method: event?.method,
      error: error.message,
      stack: error.stack
    })
    
    // 发送到日志服务
    await logToService({
      level: 'error',
      message: error.message,
      stack: error.stack,
      context: {
        requestId: event?.context?.requestId,
        path: event?.path
      }
    })
  })
  
  // 请求结束时的钩子
  nitroApp.hooks.hook('afterResponse', (event) => {
    const duration = Date.now() - (event.context.requestStart || Date.now())
    
    // 记录慢请求
    if (duration > 3000) {
      console.warn(`Slow request: ${event.path} took ${duration}ms`)
    }
  })
})

function generateRequestId(): string {
  return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`
}

async function logToService(log: any) {
  // 发送到日志服务(如 Elasticsearch、CloudWatch 等)
}

API 路由统一错误中间件

// server/utils/api-handler.ts
type ApiHandler<T> = (event: H3Event) => Promise<T>

export function defineApiHandler<T>(handler: ApiHandler<T>) {
  return defineEventHandler(async (event) => {
    try {
      const result = await handler(event)
      return {
        success: true,
        data: result
      }
    } catch (error: any) {
      // 已知的业务错误
      if (error.statusCode) {
        setResponseStatus(event, error.statusCode)
        return {
          success: false,
          error: {
            code: error.statusCode.toString(),
            message: error.message
          }
        }
      }
      
      // 未知错误
      console.error('API Error:', error)
      setResponseStatus(event, 500)
      
      return {
        success: false,
        error: {
          code: 'INTERNAL_ERROR',
          message: process.dev ? error.message : '服务器内部错误'
        }
      }
    }
  })
}

使用方式:

// server/api/users/[id].ts
export default defineApiHandler(async (event) => {
  const id = getRouterParam(event, 'id')
  
  const user = await prisma.user.findUnique({ where: { id } })
  
  if (!user) {
    throw createError({
      statusCode: 404,
      message: '用户不存在'
    })
  }
  
  return user
})

错误日志与监控

结构化日志系统

// utils/logger.ts
type LogLevel = 'debug' | 'info' | 'warn' | 'error'

interface LogEntry {
  level: LogLevel
  message: string
  timestamp: string
  context?: Record<string, any>
  error?: {
    name: string
    message: string
    stack?: string
  }
}

class Logger {
  private context: Record<string, any> = {}
  
  setContext(ctx: Record<string, any>) {
    this.context = { ...this.context, ...ctx }
  }
  
  private log(level: LogLevel, message: string, data?: any) {
    const entry: LogEntry = {
      level,
      message,
      timestamp: new Date().toISOString(),
      context: { ...this.context, ...data }
    }
    
    if (data instanceof Error) {
      entry.error = {
        name: data.name,
        message: data.message,
        stack: data.stack
      }
    }
    
    // 开发环境输出到控制台
    if (process.dev) {
      const method = level === 'error' ? 'error' : level === 'warn' ? 'warn' : 'log'
      console[method](`[${level.toUpperCase()}]`, message, entry)
    }
    
    // 生产环境发送到日志服务
    if (process.env.NODE_ENV === 'production') {
      this.sendToLogService(entry)
    }
  }
  
  private async sendToLogService(entry: LogEntry) {
    try {
      await $fetch('/api/logs', {
        method: 'POST',
        body: entry
      })
    } catch (e) {
      console.error('Failed to send log:', e)
    }
  }
  
  debug(message: string, data?: any) {
    this.log('debug', message, data)
  }
  
  info(message: string, data?: any) {
    this.log('info', message, data)
  }
  
  warn(message: string, data?: any) {
    this.log('warn', message, data)
  }
  
  error(message: string, error?: Error | any) {
    this.log('error', message, error)
  }
}

export const logger = new Logger()

集成 Sentry

// plugins/sentry.client.ts
import * as Sentry from '@sentry/vue'

export default defineNuxtPlugin((nuxtApp) => {
  const config = useRuntimeConfig()
  
  if (!config.public.sentryDsn) {
    return
  }
  
  Sentry.init({
    app: nuxtApp.vueApp,
    dsn: config.public.sentryDsn,
    environment: config.public.environment,
    
    integrations: [
      Sentry.browserTracingIntegration({
        router: useRouter()
      }),
      Sentry.replayIntegration()
    ],
    
    // 采样率
    tracesSampleRate: 0.1,
    replaysSessionSampleRate: 0.1,
    replaysOnErrorSampleRate: 1.0,
    
    // 过滤不需要上报的错误
    beforeSend(event, hint) {
      const error = hint.originalException
      
      // 忽略特定错误
      if (error instanceof Error) {
        if (error.message.includes('Network Error')) {
          return null
        }
        if (error.message.includes('ResizeObserver')) {
          return null
        }
      }
      
      return event
    }
  })
  
  // 添加用户信息
  const { user } = useUserStore()
  if (user) {
    Sentry.setUser({
      id: user.id,
      email: user.email
    })
  }
})
// nuxt.config.ts
export default defineNuxtConfig({
  runtimeConfig: {
    public: {
      sentryDsn: process.env.SENTRY_DSN,
      environment: process.env.NODE_ENV
    }
  }
})

最佳实践总结

错误处理层次

┌─────────────────────────────────────────────────────┐
│                   error.vue                          │
│              (致命错误兜底页面)                        │
└────────────────────┬────────────────────────────────┘
                     │
┌────────────────────┴────────────────────────────────┐
│              全局错误插件                             │
│         (Vue errorHandler + Nuxt hooks)             │
└────────────────────┬────────────────────────────────┘
                     │
┌────────────────────┴────────────────────────────────┐
│              ErrorBoundary 组件                      │
│            (组件级错误隔离)                          │
└────────────────────┬────────────────────────────────┘
                     │
┌────────────────────┴────────────────────────────────┐
│              Try-Catch / Error 处理                  │
│           (业务代码级错误处理)                        │
└─────────────────────────────────────────────────────┘

检查清单

基础配置:

  • 创建 error.vue 全局错误页面
  • 针对 404、403、500 等常见错误定制显示
  • 开发环境显示错误堆栈

组件级处理:

  • 为关键组件添加 ErrorBoundary
  • 实现组件级错误降级方案
  • 支持错误重试机制

API 错误:

  • 封装统一的请求错误处理
  • API 返回统一的错误格式
  • 实现请求重试逻辑

监控上报:

  • 集成错误监控服务(Sentry 等)
  • 实现结构化日志记录
  • 设置错误告警机制

用户体验:

  • 错误提示友好且可操作
  • 提供返回/重试等恢复选项
  • 避免暴露敏感技术信息

总结

Nuxt 的错误处理涉及多个层面:

  1. error.vue:全局致命错误的兜底页面
  2. createError / showError:主动抛出格式化错误
  3. ErrorBoundary:组件级错误隔离
  4. 插件钩子:全局错误监听与上报
  5. API 错误处理:请求层面的统一处理

核心原则是:让错误可预测、可追踪、可恢复

通过合理的错误处理架构,既能保证应用的健壮性,又能在出错时给用户提供良好的体验,同时为开发团队提供足够的调试信息。