探索 Nuxt 的 useError:SSR 友好的全局错误状态(Nuxt 全栈框架实践指南)
在 Nuxt 这样的全栈框架中,同一个应用会同时运行在服务端(SSR)与客户端(CSR),因此错误状态也需要一套能在两端共享、且能随 SSR payload 平滑传输的机制。useError 就是 Nuxt 暴露给应用层读取"当前正在被处理的全局 Nuxt 错误"的官方 composable。
本指南以 use-error.md 文档为骨架,结合仓库内真实实现源码,帮助你掌握 useError 的类型契约、在组件/插件/中间件中的正确用法,以及它与 createError、showError、clearError 和 error.vue 错误页之间的协作关系,从而搭建一套可诊断、可恢复的全栈错误处理方案。
useError 是什么
useError 是 Nuxt 提供的全局错误访问 composable,它的职责非常单一:
返回当前正在被 Nuxt 处理的全局错误,客户端与服务端均可使用,并提供一个响应式(reactive)、SSR 友好的错误状态。
其核心使用方式只有一行:
const error = useError()
你可以在组件的 <script setup>、页面、插件乃至路由中间件中调用它,从而读取或响应当前的 Nuxt 错误。由于 Nuxt 会对 composable 做自动导入,因此在 imports/presets.ts 中注册后,绝大多数场景下无需手动 import 即可直接使用 useError(同一自动导入清单中还包含 createError、showError、clearError、isNuxtError)。
读取错误状态
在页面或组件中,最直接的应用是判断当前是否存在错误,并据此展示不同的 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 在错误处理文档中特别提示:渲染错误页是一次全新的页面加载,这意味着所有已注册的中间件都会重新执行一次。此时如果中间件依赖于被错误打断的插件能力(如 $route、useRouter),可能并不安全;但你可以利用 useError 在中间件里判断当前是否正在处理某个错误,从而决定是否跳过某些逻辑。这与仓库中 check-if-page-unused.ts、check-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.value 为 undefined。
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清洗,非法值(例如字符串或越界状态码)会被回退为500;statusText同样会经过清洗处理(见 error.ts)。NuxtError扩展自原生Error,但其namegetter 被固定返回字符串HTTPError。源码注释解释了原因:h3 是通过构造函数名而非instanceof来识别自身错误的,因此 SSR 期间抛出的错误只有保持name === 'HTTPError',才能被正确映射到对应的 HTTP 响应(见 error.ts)。- 为了向后兼容,
NuxtError还保留了statusCode与statusMessage两个 getter,分别代理到status与statusText,但已被标记为@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
由此可以推导出三个关键事实:
- 错误状态存放于
NuxtPayload.error。NuxtPayload是 SSR 期间生成、随页面 HTML 注入并传输给客户端的全局数据容器,其接口中显式声明了error?: NuxtError | undefined(见 types.ts)。因此,useError在服务端读到的错误会被序列化进 payload,客户端 hydration 后继续读取同一个对象——这就是"SSR 友好、两端一致"的底层机制。 - 共享的是同一份状态。
showError、createError写入错误、clearError清除错误,都是围绕payload.error这个同一来源进行的,因此任何写入都能被useError观察到。这也是为什么 nuxt-root.vue(Nuxt 应用根组件)中只调用一次const error = useError(),就能驱动"渲染正常应用还是渲染错误页"的分支判断(见 nuxt-root.vue 及其对错误组件#build/error-component.mjs的渲染)。 - 错误必须能被序列化识别。
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.status、error.message 与 error.data,决定展示"404 未找到""5xx 服务错误"还是带自定义数据的降级页。
最佳实践小结
- 用
error.value判断错误存在性:无错误时useError()返回undefined,存在时返回完整的NuxtError对象。 - 类型使用
NuxtError:需要声明 prop 或变量类型时,从#app导入NuxtError。 - 自定义字段放进
data,statusText只放简短 ASCII 状态文本,长描述放message。 - 依赖插件的 API 要谨慎:若插件已抛错,在错误被清除前它不会重新执行,此时
$route、useRouter等可能不可用;错误页是新的一次页面加载,中间件会再次运行,可在其中用useError判断是否正处于错误处理流程。 - 掌握写、读、清的闭环:
createError/showError写入、useError读取、clearError清除,全部围绕同一个 SSR payload 中的error字段,这正是 Nuxt 全栈错误状态"两端一致、可响应式驱动 UI"的根基。
延伸阅读
- 错误处理完整指南:系统讲解 Vue 错误、启动错误、Nitro 服务端错误与 JS chunk 错误的处理策略
- error.vue 目录结构说明:自定义错误页及其
errorprop 的字段约定 - createError 工具 与 showError 工具:全局错误的"写入端"
- clearError 工具:清除错误并可选重定向
- 源码参考:useError 实现、NuxtError 类型契约与 NuxtPayload、根组件对错误状态的分派
通过将 useError 与 createError、showError、clearError 组合使用,并在 error.vue、NuxtErrorBoundary 中正确消费错误对象,你就能在 Nuxt 应用中构建一套覆盖 SSR 与 CSR、可响应式驱动界面、可平滑恢复的全栈错误处理体系。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0627
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00