Nuxt error.vue 深度解析:自定义错误页与 NuxtError 错误的完整处理机制
本文基于 Nuxt 官方目录结构文档 error.vue 展开,讲清楚 app/error.vue 文件如何接管应用运行时的所有错误渲染:从 NuxtError 对象的字段含义、createError 的用法,到该文件在构建时如何被解析、在运行时如何被挂载渲染,并对照 Nuxt 源码解释错误页触发、清除与爬虫友好的完整调用链。
error.vue 是什么:接管默认错误页的入口文件
在应用生命周期中,总有一些错误会在运行时意外出现:路由不存在、useFetch 抛出的 HTTP 错误、插件或组件里的未捕获异常。Nuxt 通过 app/error.vue 文件让你覆盖默认错误页,以统一、友好的方式呈现这些错误。
一个最小的自定义错误页写法如下(与官方文档示例一致):
<script setup lang="ts">
import type { NuxtError } from '#app'
const props = defineProps<{ error: NuxtError }>()
</script>
<template>
<div>
<h1>{{ error.status }}</h1>
<NuxtLink to="/">Go back home</NuxtLink>
</div>
</template>
文件需要放在应用的 app/ 目录(默认即 ~/app/error.vue)下。它只有一个 prop——error,类型是 NuxtError,其中包含了你要处理的全部错误信息。
它不是路由:为什么不能放进 pages 目录
官方文档特别强调了一个容易被误解的点:虽然叫 "error page",但它不是一个路由,不应放在 ~/pages 目录中;同理,也不应在这个文件里使用 definePageMeta。原因从源码结构可以直接印证:error.vue 不参与文件路由系统的解析,而是作为独立组件被挂载到应用根组件内部——当 nuxt-root 检测到 payload.error 有值时,用错误页组件替换掉正常的应用组件树(见 nuxt-root.vue 中 ErrorComponent v-else-if="error" 的分支逻辑)。
不过文档同时指出:你可以在错误页里使用布局,方式是借助 <NuxtLayout> 组件并指定布局名称,从而让错误页复用应用的整体外观(例如导航栏、主题样式)。
错误解析流程:源码如何找到你的 error.vue
从源码看,error.vue 的解析发生在构建准备阶段。core/app.ts 中的 resolveAppComponents 会按 layer 顺序查找 error 文件,找不到时回退到内置默认错误页:
// packages/nuxt/src/core/app.ts
// Resolve error component
app.errorComponent ||= await findPath(layerDirs.map(d => join(d.app, 'error')))
?? resolve(nuxt.options.appDir, 'components/nuxt-error-page.vue')
这段代码说明了两个重要行为:
- 层级(Layers)优先:
findPath会遍历所有 layer 的app/目录,因此基座 layer 的error.vue会被上层应用同名的文件覆盖——这与 Nuxt 其他配置合并规则一致; - 内置兜底:项目没有提供
app/error.vue时,Nuxt 使用内置的 nuxt-error-page.vue 作为错误页。该默认页面会按状态码选择模板:status === 404时使用 error-404.vue,其余错误使用error-500.vue,并在开发模式下展示堆栈信息。
解析结果随后被 core/templates.ts 中的 errorComponentTemplate 生成 #build/error-component.mjs 虚拟模块,供根组件 import 使用:
export const errorComponentTemplate: NuxtTemplate = {
filename: 'error-component.mjs',
dependsOn: [],
getContents: ctx => genExport(ctx.app.errorComponent!, ['default']),
}
开发模式下,对 error.vue 的增删改还有专门的监听处理:core/nuxt.ts 将 error.vue 与 app.vue、app.config.ts 并列为核心文件,新增或删除时会触发 Nuxt 重启;core/builder.ts 则会在文件变更时清空缓存的 app.errorComponent,让解析结果重新生效。这意味着修改 error.vue 后,开发服务器会可靠地重新解析,不会出现“改了错误页不生效”的问题。
NuxtError 对象:error prop 的完整字段
官方文档给出了错误页 error prop 的核心类型。结合 app/types.ts 中的完整声明,NuxtError 在文档版本基础上还包含 headers 与 body 等面向 HTTP 响应的字段:
interface NuxtError {
status: number // HTTP 状态码,范围 [100...599]
fatal: boolean // 是否为致命错误(未处理或显式标记 fatal 时为 true)
unhandled: boolean // 是否未被应用处理
statusText?: string // HTTP 状态文本(reason phrase)
data?: unknown // 附加数据,会序列化进错误 JSON 的 data 字段
cause?: unknown // 错误链上的原始错误
}
各字段的语义如下:
| 字段 | 类型 | 说明 |
|---|---|---|
status |
number |
HTTP 状态码,缺省为 500(见 createError 实现) |
statusText |
string? |
HTTP 状态文本,如 Not Found |
fatal |
boolean |
致命错误标记;源码中默认取 details.fatal ?? !!this.unhandled |
unhandled |
boolean? |
错误未被应用捕获处理时置为 true |
data |
unknown |
自定义数据,会随错误 JSON 序列化 |
cause |
unknown |
原始错误对象(错误链) |
headers / body |
— | 完整版类型中附加的 HTTP 头与响应体字段(见 types.ts) |
值得注意的序列化细节:NuxtError 的 toJSON() 方法在错误被标记为 unhandled 时不会输出 message 与 data(消息统一为 'HTTPError'),这是避免将内部错误细节泄露给客户端的安全设计(见 error.ts 中 toJSON 实现)。因此在错误页中读取 error.data 时,应意识到 SSR 场景下序列化后的对象可能不含这部分信息。
另外,NuxtError 类刻意将 name 覆盖为 HTTPError——因为 h3/Nitro 按构造器名识别错误类型,SSR 过程中抛出的 Nuxt 错误只有保持这个名字才能被映射为正确的 HTTP 响应状态码。
制造错误:createError 与 data 字段的使用规范
官方文档强调了一条重要的实践规范:如果你要抛出一个带自定义字段的错误,自定义字段会丢失,应该把它们放进 data。正确写法:
throw createError({
status: 404,
statusText: 'Page Not Found',
data: {
myCustomField: true,
},
})
createError 的实现在 composables/error.ts 中,它接受字符串、Error 或错误详情对象三种输入,并做了幂等处理(传入已是 NuxtError 时直接返回):
export const createError = <DataT = unknown>(error: string | Error | NuxtErrorDetails<DataT>): NuxtError<DataT> => {
if (isNuxtError<DataT>(error)) { return error }
return typeof error === 'string'
? new NuxtError<DataT>(error)
: new NuxtError<DataT>(error.message ?? '', error as NuxtErrorDetails<DataT>)
}
从 NuxtError 构造函数可以看出几个默认值行为,供你在使用时参考:
status缺省时回退到cause的 status,最终缺省 500,并经过sanitizeStatusCode约束在[100...599]范围内;message的推断顺序为:显式message→cause.message→statusText→ 由'HTTPError status statusText'拼接兜底;- 旧的
statusCode/statusMessage字段仍被接受,但已标记为 deprecated,新代码应使用status/statusText。
配合错误页的 data 约定,错误页中就可以安全地读取 error.data 来渲染业务化信息,例如区分“页面不存在”与“登录过期”。
错误的运行时生命周期:从抛出到渲染
理解了文件与类型后,再看错误页是如何被触发和清除的。整条链路可以从 nuxt-root.vue 入手:
<Suspense @resolve="onResolve">
<ErrorComponent v-else-if="error" :error="error" />
<AppComponent v-else />
</Suspense>
其中 error 来自 useError()(即 nuxtApp.payload.error 的响应式引用)。根组件注册了 onErrorCaptured:
- 服务端渲染(SSR)中捕获到的任何组件错误,都会调用
showError(err)写入 payload,并返回false阻止错误中断渲染——这正是 SSR 能返回带状态码的完整错误页 HTML 的原因; - 客户端捕获的致命或未处理(
fatal || unhandled)的 Nuxt 错误同样触发showError; - 特殊处理:如果当前是爬虫 User-Agent,Nuxt 只调用
app:errorhook 通知错误而不渲染错误页(_notifyCrawlerError),让爬虫索引到服务端已渲染的 HTML 而不是错误页。
showError 与 clearError
两个关键 composable 均在 composables/error.ts:
export const showError = <DataT = unknown>(
error: string | Error | NuxtErrorDetails<DataT>,
): NuxtError<DataT> => {
const nuxtError = createError<DataT>(error)
// 客户端触发 app:error hook
// error.value ||= nuxtError → 写入 payload.error,驱动 error.vue 渲染
}
showError 将错误写入 payload.error,nuxt-root 检测到该值后立刻用你的 error.vue 替换应用组件树。对应地,clearError 会触发 app:error:cleared hook、可选地执行 router.replace(redirect) 重定向,然后把 payload.error 置空,错误页随之消失、应用恢复正常渲染。
插件与入口层面的错误兜底
错误页的覆盖面不止组件错误。从源码结构看:
- entry.ts 在服务端和客户端两个入口都捕获插件执行与挂载阶段的异常,统一走
app:errorhook 并写入nuxt.payload.error; - nuxt.ts 中
applyPlugins循环内的注释“short circuit if we are not renderingerror.vue”表明:当已有错误待渲染时,后续插件抛出的异常会被短路合并,避免掩盖最初的错误,保证错误页展示的仍是第一个根因错误。
相关的 Hook
在错误处理链路上,可以订阅以下运行时 Hook 做日志、监控上报:
| Hook | 触发时机 |
|---|---|
app:error |
错误被创建并准备渲染错误页时(SSR 与客户端均触发) |
app:error:cleared |
clearError 被调用、错误清除时 |
vue:error |
通过 Vue 的 onErrorCaptured 捕获到组件级错误时 |
默认错误页长什么样:当你不提供 error.vue 时
没有自定义 error.vue 时,Nuxt 使用的内置错误页 nuxt-error-page.vue 做了不少默认处理,值得了解以便决定是否需要覆盖:
const status = Number(_error.status || 500)
const is404 = status === 404
const statusText = _error.statusText ?? (is404 ? 'Page Not Found' : 'Internal Server Error')
const description = _error.message || _error.toString()
const stack = import.meta.dev && !is404 ? _error.description || `<pre>${stacktrace}</pre>` : undefined
即:404 展示“Page Not Found”模板,其他错误展示“Internal Server Error”模板,且仅在开发模式下对非 404 错误附带格式化堆栈(内部模块行以特殊样式标注)。模板本身由 ui-templates 包生成,404 页还内联了 modulepreload 补丁和“返回上一页”的交互脚本。如果你的业务错误页只需要定制文案与样式,参照这份默认实现即可快速上手。
小结与实践要点
app/error.vue是错误页的唯一自定义入口:放在app/目录(可被 layer 覆盖),不要放进pages/,不要使用definePageMeta;布局需求用<NuxtLayout>组件实现。- 错误页通过
errorprop 接收NuxtError;自定义业务字段请放入createError({ ..., data: {...} })的data中,否则会在序列化时丢失。 - 错误页的触发不依赖路由系统:
payload.error有值时,nuxt-root.vue 会用错误页组件替换整个应用组件树;showError/clearError是驱动其出现与消失的官方 API。 - 未处理错误在序列化为 HTTP 响应时会隐藏
message与data,错误页设计时应对error.data可能为undefined的情况保持容忍。 - 爬虫访问时 Nuxt 有意不渲染错误页,只通过
app:errorhook 通知,保证 SEO 内容不被错误页覆盖。
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