错误处理

了解如何在 Nuxt 中捕获和处理错误。

Nuxt 是全栈框架,这意味着在不同场景下可能出现多种不可避免的用户运行时错误:

  • Vue 渲染生命周期中的错误(SSR & CSR)
  • 服务端和客户端启动错误(SSR + CSR)
  • Nitro 服务端生命周期中的错误(server/ 目录)
  • 下载 JS chunk 时的错误

SSR 指服务端渲染,CSR 指客户端渲染。

Vue 错误

你可以使用 onErrorCaptured 来捕获 Vue 错误。

此外,Nuxt 提供了 vue:error hook,当任何错误冒泡到顶层时会被调用。

如果你使用错误上报框架,可以通过 vueApp.config.errorHandler 提供全局处理器。它会接收所有 Vue 错误,即使已经被处理过。

export default defineNuxtPlugin((nuxtApp) => {
  nuxtApp.vueApp.config.errorHandler = (error, instance, info) => {
    // 处理错误,比如上报到服务
  }

  // 也可以这样
  nuxtApp.hook('vue:error', (error, instance, info) => {
    // 处理错误,比如上报到服务
  })
})

注意 vue:error hook 基于 onErrorCaptured 生命周期 hook。

启动错误

如果 Nuxt 应用启动过程中出现任何错误,Nuxt 会调用 app:error hook。

这包括:

  • 运行 Nuxt 插件
  • 处理 app:created 和 app:beforeMount hooks
  • 将 Vue 应用渲染为 HTML(SSR 期间)
  • 挂载应用(客户端),不过这种情况你应该用 onErrorCaptured 或 vue:error 处理
  • 处理 app:mounted hook

Nitro 服务端错误

目前你无法为这些错误定义服务端处理器,但可以渲染错误页面,参考渲染错误页面章节。

JS Chunk 加载错误

你可能会遇到 chunk 加载错误,原因可能是网络连接失败或新部署使旧的带 hash 的 JS chunk URL 失效。Nuxt 内置了处理 chunk 加载错误的支持:路由导航时 chunk 加载失败会自动硬刷新页面。

你可以通过设置 experimental.emitRouteChunkError 为 false(完全禁用这些错误的 hook)或 manual(自己处理)来改变这个行为。如果你想手动处理 chunk 加载错误,可以参考自动实现获取思路。

错误页面

当 Nuxt 遇到致命错误时(服务端的任何未处理错误,或客户端用 fatal: true 创建的错误),它会返回 JSON 响应(如果请求带 Accept: application/json 头)或触发全屏错误页面。

服务端生命周期中可能出错的情况:

  • 处理 Nuxt 插件时
  • 将 Vue 应用渲染为 HTML 时
  • 服务端 API 路由抛出错误时

客户端也可能出错的情况:

  • 处理 Nuxt 插件时
  • 挂载应用之前(app:beforeMount hook)
  • 挂载应用时(如果错误没有被 onErrorCaptured 或 vue:error hook 处理)
  • Vue 应用在浏览器中初始化和挂载后(app:mounted)

查看所有 Nuxt 生命周期 hooks。

在应用源码目录中添加 ~/error.vue(和 app.vue 同级)来自定义默认错误页面。

<script setup lang="ts">
import type { NuxtError } from '#app'

const props = defineProps({
  error: Object as () => NuxtError,
})

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

<template>
  <div>
    <h2>{{ error?.status }}</h2>
    <button @click="handleError">
      清除错误
    </button>
  </div>
</template>

阅读更多关于 error.vue 及其用途的内容。

对于自定义错误,我们强烈推荐使用 onErrorCaptured composable(可以在页面/组件的 setup 函数中调用)或 vue:error 运行时 nuxt hook(可以在 nuxt 插件中配置)。

export default defineNuxtPlugin((nuxtApp) => {
  nuxtApp.hook('vue:error', (err) => {
    //
  })
})

当你准备好移除错误页面时,可以调用 clearError 辅助函数,它接受一个可选的重定向路径(比如导航到一个"安全"页面)。

在使用任何依赖 Nuxt 插件的东西之前,比如 $route 或 useRouter,先检查一下——如果插件抛出了错误,在清除错误之前它不会重新运行。

渲染错误页面是完全独立的页面加载,意味着所有已注册的中间件都会重新运行。你可以在中间件中使用 useError 来检查是否正在处理错误。

如果你在 Node 16 上运行,并且在渲染错误页面时设置了任何 cookie,它们会覆盖之前设置的 cookie。推荐使用更新版本的 Node,因为 Node 16 已于 2023 年 9 月停止维护。

错误工具函数

useError

function useError (): Ref<Error | { url, status, statusText, message, description, data }>

这个函数返回当前正在处理的全局 Nuxt 错误。

阅读更多关于 useError composable 的内容。

createError

function createError (err: string | { cause, data, message, name, stack, status, statusText, fatal }): Error

创建带额外元数据的错误对象。你可以传入一个字符串作为错误 message,或者传入包含错误属性的对象。它在应用的 Vue 和服务端部分都可用,用于抛出。

如果你抛出用 createError 创建的错误:

  • 服务端会触发全屏错误页面,可以用 clearError 清除。
  • 客户端会抛出一个非致命错误供你处理。如果需要触发全屏错误页面,设置 fatal: true。

开发环境下,错误的 cause 会被保留并暴露在错误页面中,方便你追踪原始错误;生产环境下,cause 永远不会包含在错误响应或错误页面 payload 中。

<script setup lang="ts">
const route = useRoute()
const { data } = await useFetch(`/api/movies/${route.params.slug}`)

if (!data.value) {
  throw createError({
    status: 404,
    statusText: '页面未找到',
  })
}
</script>

statusText 属性用于简短的、符合 HTTP 规范的状态文本(比如 "Not Found")。它应该只包含制表符、空格和可见 ASCII 字符([\t\u0020-\u007E])。

对于任何详细描述、多行消息或包含非 ASCII 字符的内容,应该使用 message 属性。

阅读更多关于 createError 工具函数的内容。

showError

function showError (err: string | Error | { status, statusText }): Error

你可以在客户端任何时候调用这个函数,或者(服务端)直接在中间件、插件或 setup() 函数中调用。它会触发全屏错误页面,可以用 clearError 清除。

推荐使用 throw createError() 代替。

阅读更多关于 showError 工具函数的内容。

clearError

function clearError (options?: { redirect?: string }): Promise<void>

这个函数清除当前正在处理的 Nuxt 错误。它还接受一个可选的重定向路径(比如导航到一个"安全"页面)。

阅读更多关于 clearError 工具函数的内容。

在组件中渲染错误

Nuxt 还提供了 <NuxtErrorBoundary> 组件,让你可以在应用内处理客户端错误,而不用整个站点都变成错误页面。

这个组件负责处理默认插槽中发生的错误。客户端会阻止错误冒泡到顶层,转而渲染 #error 插槽。

#error 插槽会接收 error 作为 prop。(如果你设置 error = null,会触发默认插槽的重新渲染;你需要确保错误已经完全解决,否则错误插槽只会再渲染一次。)

如果你导航到其他路由,错误会自动清除。

<template>
  <!-- 一些内容 -->
  <NuxtErrorBoundary @error="someErrorLogger">
    <!-- 默认插槽渲染你的内容 -->
    <template #error="{ error, clearError }">
      你可以在这里本地显示错误: {{ error }}
      <button @click="clearError">
        清除错误
      </button>
    </template>
  </NuxtErrorBoundary>
</template>