首页
/ 探索 Nuxt 的 useError:SSR 友好的全局错误状态(Nuxt 全栈框架实践指南)

探索 Nuxt 的 useError:SSR 友好的全局错误状态(Nuxt 全栈框架实践指南)

2026-09-07 14:17:12作者:明树来

在 Nuxt 这样的全栈框架中,同一个应用会同时运行在服务端(SSR)客户端(CSR),因此错误状态也需要一套能在两端共享、且能随 SSR payload 平滑传输的机制。useError 就是 Nuxt 暴露给应用层读取"当前正在被处理的全局 Nuxt 错误"的官方 composable。

本指南以 use-error.md 文档为骨架,结合仓库内真实实现源码,帮助你掌握 useError 的类型契约、在组件/插件/中间件中的正确用法,以及它与 createErrorshowErrorclearErrorerror.vue 错误页之间的协作关系,从而搭建一套可诊断、可恢复的全栈错误处理方案。

useError 是什么

useError 是 Nuxt 提供的全局错误访问 composable,它的职责非常单一:

返回当前正在被 Nuxt 处理的全局错误,客户端与服务端均可使用,并提供一个响应式(reactive)、SSR 友好的错误状态。

其核心使用方式只有一行:

const error = useError()

你可以在组件的 <script setup>、页面、插件乃至路由中间件中调用它,从而读取或响应当前的 Nuxt 错误。由于 Nuxt 会对 composable 做自动导入,因此在 imports/presets.ts 中注册后,绝大多数场景下无需手动 import 即可直接使用 useError(同一自动导入清单中还包含 createErrorshowErrorclearErrorisNuxtError)。

读取错误状态

在页面或组件中,最直接的应用是判断当前是否存在错误,并据此展示不同的 UI:

<script setup lang="ts">
const error = useError()

if (error.value) {
  console.error('Nuxt error:', error.value)
}
</script>

需要注意:useError 返回的是一个 Ref,因此必须通过 error.value 访问真实错误对象;它自身是响应式的,当 Nuxt 的错误状态发生变化(被设置或清除)时,error.value 会自动更新。

在中间件中使用

Nuxt 在错误处理文档中特别提示:渲染错误页是一次全新的页面加载,这意味着所有已注册的中间件都会重新执行一次。此时如果中间件依赖于被错误打断的插件能力(如 $routeuseRouter),可能并不安全;但你可以利用 useError 在中间件里判断当前是否正在处理某个错误,从而决定是否跳过某些逻辑。这与仓库中 check-if-page-unused.tscheck-if-layout-used.ts 等内置插件读取全局错误状态的模式一脉相承。

类型契约

根据 use-error.md 中的类型声明,useError 的签名与错误对象结构为:

interface NuxtError<DataT = unknown> {
  status: number
  statusText?: string
  message: string
  data?: DataT
  cause?: unknown
  fatal: boolean
}

export const useError: () => Ref<NuxtError | undefined>

也就是说,useError 不接受任何参数,返回值为 Ref<NuxtError | undefined>——当没有错误时,error.valueundefined

NuxtError 的完整字段(源码级)

上述接口是文档给出的"最小公共契约",而实际错误对象由 error.ts 中的 NuxtError 类构造。对照它的字段声明,我们可以更完整地理解每个字段的含义:

class NuxtError<DataT = unknown> extends Error {
  readonly status: number            // HTTP 状态码,范围 [100...599]
  readonly statusText: string | undefined  // HTTP 状态文本(原因短语)
  readonly headers: Headers | undefined    // 随错误响应发送的额外 HTTP 头
  readonly data: DataT | undefined         // 附加到错误 JSON body data 字段的数据
  readonly body: Record<string, unknown> | undefined // 附加到错误 JSON body 顶层的属性
  readonly unhandled: boolean | undefined  // 该错误是否未被应用处理
  readonly fatal: boolean                  // 是否触发全屏错误页
  override readonly cause: unknown
}

需要留意的几个细节:

  • status 在构造时会经过 sanitizeStatusCode 清洗,非法值(例如字符串或越界状态码)会被回退为 500statusText 同样会经过清洗处理(见 error.ts)。
  • NuxtError 扩展自原生 Error,但其 name getter 被固定返回字符串 HTTPError。源码注释解释了原因:h3 是通过构造函数名而非 instanceof 来识别自身错误的,因此 SSR 期间抛出的错误只有保持 name === 'HTTPError',才能被正确映射到对应的 HTTP 响应(见 error.ts)。
  • 为了向后兼容,NuxtError 还保留了 statusCodestatusMessage 两个 getter,分别代理到 statusstatusText,但已被标记为 @deprecated,官方建议一律改用 status / statusText

类型与运行时的边界

  • NuxtError 的类型级"合同"定义在 types.ts,声明了 readonly __nuxt_error: true 这一标识字段,并保持与 h3 的 HTTPError 结构兼容,因为 h3 与 Nitro 正是通过读取该结构来识别 SSR 中抛出的错误。
  • NuxtError 需要作为 HTTP 响应 JSON 发送时,会通过 toJSON() 序列化(见 error.ts):若错误 unhandled,则 message 固定为 HTTPError 且不携带 data,避免向客户端泄露未处理错误的内部细节;序列化产物的类型 NuxtErrorJSON 定义在 types.ts

参数与返回值

项目 说明
参数 无。useError() 不接受任何参数
返回值 Ref<NuxtError | undefined>:包含当前 Nuxt 错误(无错误时为 undefined)的响应式引用

返回值之所以是"响应式且 SSR 友好"的,根源在于它的实现(见下节)。

深入原理:一行实现背后的 SSR 传输机制

useError 的完整实现极其精简(见 error.ts):

export const useError = (): Ref<NuxtPayload['error']> => toRef(useNuxtApp().payload, 'error')

它实际是:

useNuxtApp().payload  // 取当前 Nuxt 应用的全局 payload 对象
toRef(payload, 'error') // 将其 error 字段包装为 Ref

由此可以推导出三个关键事实:

  1. 错误状态存放于 NuxtPayload.errorNuxtPayload 是 SSR 期间生成、随页面 HTML 注入并传输给客户端的全局数据容器,其接口中显式声明了 error?: NuxtError | undefined(见 types.ts)。因此,useError 在服务端读到的错误会被序列化进 payload,客户端 hydration 后继续读取同一个对象——这就是"SSR 友好、两端一致"的底层机制。
  2. 共享的是同一份状态showErrorcreateError 写入错误、clearError 清除错误,都是围绕 payload.error 这个同一来源进行的,因此任何写入都能被 useError 观察到。这也是为什么 nuxt-root.vue(Nuxt 应用根组件)中只调用一次 const error = useError(),就能驱动"渲染正常应用还是渲染错误页"的分支判断(见 nuxt-root.vue 及其对错误组件 #build/error-component.mjs 的渲染)。
  3. 错误必须能被序列化识别NuxtError 上带有签名常量 NUXT_ERROR_SIGNATURE = '__nuxt_error'(见 error.ts),配合工具函数 isNuxtError(通过判断该签名是否存在来收窄类型,见 error.ts),框架可在 SSR 边界两侧可靠地判断某个对象是否为 Nuxt 错误。

在应用启动流程中的角色

nuxt-root.vue 的源码可以看到,error 状态被用于:判断 SSR 阶段是否因插件抛错而中止渲染(abortRender);错误传播到顶层时,依据 isNuxtError(err) && (err.fatal || err.unhandled) 决定是否渲染全屏错误页。这正是错误处理指南中所描述的:当 Nuxt 遇到 fatal 错误(服务端任意未处理错误,或客户端 fatal: true 的错误)时,会渲染全屏错误页;useError 读取的正是驱动这一分支的全局状态。

useError 与错误处理工具链的协作

单看 useError 只是"读",但 Nuxt 的错误体系是一个"写—读—清"的闭环。掌握下面三个工具如何操作同一个 payload.error,才能真正用好 useError

createError:创建携带元数据的错误

createError 接受一个字符串(作为 message)或包含错误属性的对象,返回一个 NuxtError,可在 Vue 与 Server 两部分使用,且设计上就是用来 throw 的(见 error.ts):

throw createError({
  status: 404,
  statusText: 'Page Not Found',
})

关于 statusText,文档有明确约束:它只应承载简短、符合 HTTP 规范的状态文本(如 "Not Found"),只能包含制表符、空格与可见 ASCII 字符;任何详细描述、多行文本或非 ASCII 内容都应放入 message 属性。若你希望给错误附带自定义业务字段,注意自定义字段会被丢弃,应把它们放进 data 中——这一点在 error.vue 文档里有明确示例:

throw createError({
  status: 404,
  statusText: 'Page Not Found',
  data: {
    myCustomField: true,
  },
})

另外从源码可知(error.ts),当传入对象本身是 Error 实例,或对象里带有 cause 时,原始错误会被保留在 cause 字段用于追踪;但只有在开发模式下,cause 才会被序列化并暴露给错误页(SerializedErrorCause 类型见 types.ts),生产环境不会在错误响应或错误页 payload 中携带 cause 链。

showError:直接触发全屏错误页

showError 可在客户端任意时刻调用,或(在服务端)在中间件、插件、setup() 中直接调用。它的实现揭示了与 useError 的关系(见 error.ts):

export const showError = (error) => {
  const nuxtError = createError(error)
  // ...
  const error = useError()          // 读取全局错误 ref
  if (import.meta.client) {
    nuxtApp.hooks.callHook('app:error', nuxtError)  // 触发错误生命周期钩子
  }
  error.value ||= nuxtError          // 仅在尚无错误时写入全局状态
  return nuxtError
}

可以看到 showError 内部正是通过 useError() 拿到全局错误引用并写入。官方建议优先使用 throw createError() 而不是直接调用 showError。服务端抛出的 createError 错误会触发全屏错误页;客户端抛出的非 fatal 错误则交由你自行处理,若确实需要全屏错误页,需显式设置 fatal: true

clearError:清除错误并可选重定向

当错误已处理完毕、准备离开错误页时,调用 clearError 清除全局错误状态(见 error.ts):

await clearError({ redirect: '/' })

其内部会依次:触发 app:error:cleared 钩子、若传入 redirect 则通过 useRouter().replace(redirect) 跳转、将 error.value 置为 undefined,并在开发环境的客户端清理可能残留的错误浮层元素。清空之后,useError() 的返回值将恢复为 undefined——这正是你的组件重新渲染正常内容的信号。

自定义错误页中的典型用法

Nuxt 允许在应用源码目录(app.vue 同级)添加 error.vue 来自定义全屏错误页。该页面通过 prop 接收 error 对象(而非 useError),常见模板如下:

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

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

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

<template>
  <div>
    <h1>{{ error.status }}</h1>
    <p>{{ error.message }}</p>
    <button @click="handleError">Clear errors</button>
  </div>
</template>

需要留意(见 error.vue 文档):error.vue 虽是"错误页",但它并不是路由,不应放入 ~/pages,也不应使用 definePageMeta。而针对局部组件内的错误(不希望整站被错误页替换),则可使用 <NuxtErrorBoundary> 组件,在其 #error 插槽内接收 error 并调用 clearError 完成局部恢复。

典型实战场景

结合文档示例与仓库实现,以下三类场景最能体现 useError 的价值:

场景一:在组件/页面中按错误态渲染内容

<script setup lang="ts">
const error = useError()

// 存在全局错误时展示降级信息,否则渲染正常内容
</script>

<template>
  <div v-if="error">
    <p>出错了:{{ error.message }}({{ error.status }})</p>
  </div>
  <div v-else>
    <!-- 正常内容 -->
  </div>
</template>

场景二:在数据请求后构造并抛出业务错误

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

if (!data.value) {
  throw createError({
    status: 404,
    statusText: 'Page Not Found',
    data: { resource: 'movie', slug: route.params.slug },
  })
}
</script>

服务端抛出该错误后会渲染全屏错误页;页面随后可通过 error.value.data 在自定义错误页或上报逻辑中读取业务字段。

场景三:在插件/应用启动错误时做诊断

由于 Nuxt 应用启动阶段的错误(插件执行、app:created/app:beforeMount、SSR 渲染 HTML、客户端挂载等)都会进入 payload.error,你可以在 error.vue 或应用级逻辑中通过 useError() 观察 error.statuserror.messageerror.data,决定展示"404 未找到""5xx 服务错误"还是带自定义数据的降级页。

最佳实践小结

  • error.value 判断错误存在性:无错误时 useError() 返回 undefined,存在时返回完整的 NuxtError 对象。
  • 类型使用 NuxtError:需要声明 prop 或变量类型时,从 #app 导入 NuxtError
  • 自定义字段放进 datastatusText 只放简短 ASCII 状态文本,长描述放 message
  • 依赖插件的 API 要谨慎:若插件已抛错,在错误被清除前它不会重新执行,此时 $routeuseRouter 等可能不可用;错误页是新的一次页面加载,中间件会再次运行,可在其中用 useError 判断是否正处于错误处理流程。
  • 掌握写、读、清的闭环createError/showError 写入、useError 读取、clearError 清除,全部围绕同一个 SSR payload 中的 error 字段,这正是 Nuxt 全栈错误状态"两端一致、可响应式驱动 UI"的根基。

延伸阅读

通过将 useErrorcreateErrorshowErrorclearError 组合使用,并在 error.vueNuxtErrorBoundary 中正确消费错误对象,你就能在 Nuxt 应用中构建一套覆盖 SSR 与 CSR、可响应式驱动界面、可平滑恢复的全栈错误处理体系。

登录后查看全文
热门项目推荐
相关项目推荐