Nuxt createError 完全指南:在 Vue 页面与 Nitro API 路由中创建、抛出并传递带元数据的错误
createError 是 Nuxt 全栈应用中用于创建「带附加元数据的错误对象」的核心工具函数,它同时适用于 Vue(客户端与 SSR)与 Nitro 服务器两大部分,且设计的最终目的就是被 throw。本文基于 create-error 参考文档,结合 error.ts 源码 与配套测试,系统讲解其参数契约、SSR/CSR 行为差异、cause 错误链传递规则,以及在 API 路由中返回结构化错误给前端的完整实践。
createError 是什么:专为「抛出」而生的错误工厂
从类型签名来看,createError 接受一个字符串或一个错误详情对象:
function createError (err: string | Error | { cause, data, message, name, stack, status, statusText, fatal }): NuxtError
它返回一个 NuxtError 实例——该实例继承自原生 Error,同时带有 status、statusText、data、fatal 等 HTTP/错误页所需的元数据字段。这一点与普通 new Error() 有本质区别:Nuxt 的渲染管线与 Nitro 服务器能够识别这个错误对象所携带的元数据,从而正确渲染错误页或返回对应的 HTTP 状态码。
从 error.ts 源码 可以看出,createError 的实现非常轻量,它其实是一个构造函数分派器:
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(源码通过NUXT_ERROR_SIGNATURE = '__nuxt_error'标记与isNuxtError判断),它会原样返回,不会二次包装。这意味着在层层传递的错误处理代码中重复调用createError是安全的。error.test.ts 中expect(createError(error)).toBe(error)正是对这条行为的验证。 - 字符串消息默认 500:若传入的是字符串,它会被当作错误的
message,而status会默认落到500(见 error.test.ts 中createError('boom').status === 500的断言)。
NuxtError 类还覆写了 name 的 getter,让它恒等于 "HTTPError"。源码注释解释了这一设计:h3 依赖构造函数名而非 instanceof 来识别错误,因此在 SSR 阶段抛出的 NuxtError 只有在 name 保持为 HTTPError 时才会被正确映射为对应的 HTTP 响应(见 error.ts)。这正是 createError 能够在服务器端被 h3/Nitro 正确识别的前提。
参数契约:支持字符串与对象的双入口
createError 的参数有两种形态:
- 字符串形态:
createError('Something went wrong')——该字符串会成为错误的message,status默认为500。 - 对象形态:
createError({ status, statusText, message, fatal, cause, data, headers, ... })——可以同时设置多个错误属性。
对象形态中可用的属性(以 NuxtErrorDetails 类型定义 为准):
| 属性 | 类型 | 说明 |
|---|---|---|
message |
string |
面向用户的详细错误描述,建议承载多行文本、非 ASCII 字符等描述性内容 |
status |
number |
HTTP 状态码,范围将被钳制在 100..599,非法值回退到 500 |
statusText |
string |
简短的 HTTP 原因短语(如 'Not Found'),只允许可见 ASCII 字符,用于客户端识别 |
cause |
unknown |
原始错误对象,用于保留被包装错误链 |
data |
unknown(泛型 DataT) |
附加到错误 JSON body data 字段的自定义数据,可回传客户端 |
fatal |
boolean |
客户端抛出时是否触发全屏错误页 |
headers |
HeadersInit |
随错误响应一起发送的附加 HTTP 头 |
name / stack |
string |
错误名与堆栈(沿原生 Error 约定) |
statusCode / statusMessage |
number / string |
已被 status / statusText 取代的废弃别名,源码中标记 @deprecated,仅用于兼容 |
字段的钳制与净化:源码如何保证 HTTP 合法性
NuxtError 构造器在 error.ts 中对 status 与 statusText 做了两层约束,全部实现在 http-status.ts:
sanitizeStatusCode:将状态码解析为数字后,若NaN或超出100..599范围则回退到默认值(此处为500)。因此createError({ status: 999 })这类越界输入不会产生非法 HTTP 响应。sanitizeStatusMessage:通过正则/[^\t\u0020-\u007E]/g剔除理由短语中不允许出现的字符(即只保留水平制表符、空格与可见 ASCII 字符)。测试 error.test.ts 中传入{ statusText: 'bad\nvalue' }的换行符正是被这一步过滤掉的。
因此官方文档反复强调的 statusText 使用规范在源码层面是有强制力的:statusText 只适合短小、符合 HTTP 规范的短语(如 "Not Found"),任何详细描述、多行文本或非 ASCII 内容都应放入 message 属性(可对照 error-handling 文档 中的 tip)。
此外,fatal 的取值逻辑也值得注意:源码中 this.fatal = details.fatal ?? !!this.unhandled,即显式传入的 fatal 优先,否则由 unhandled 推导。测试 error.test.ts 完整覆盖了这一规则:{ unhandled: true } 会得到 fatal: true,而 { fatal: false, unhandled: true } 会尊重显式的 false。
在 Vue 应用中使用:SSR 与 CSR 的截然不同行为
createError 可以在页面、组件、插件、middleware 中任意使用,但同一段 throw createError() 代码在服务端与客户端的行为完全不同:
- 服务端(SSR):抛出后直接触发全屏错误页,可通过
clearError清除错误并(可选)重定向。 - 客户端(CSR):抛出的是非致命错误(non-fatal),交由你来处理(例如用
<NuxtErrorBoundary>局部兜底,或自行捕获);若确实想触发全屏错误页,必须显式设置fatal: true。
典型场景:数据加载失败时返回 404
官方文档给出的电影详情页示例是最经典的用法——在 useFetch 拿不到数据时抛出带状态的 createError:
<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' })
}
</script>
这段代码在服务端渲染该页面时若 API 未返回数据,会直接进入 404 错误页流程;在纯客户端导航场景下,由于未设置 fatal,该错误默认为非致命,Nuxt 会走客户端错误处理路径(例如冒泡到 <NuxtErrorBoundary> 的 #error 插槽)。
与错误处理体系的其他成员协同
createError 并非孤立工具,它与 Nuxt 错误处理体系的其他 API 组成了一条完整链路(详见 12.error-handling.md):
showError:同样基于createError实现(源码中showError的第一步就是createError(error)),可直接在客户端任意时刻或服务端 middleware/plugins/setup()中触发全屏错误页。官方建议更推荐使用throw createError()而非showError。clearError:清除当前被处理的 Nuxt 错误,可选携带{ redirect }跳转到"安全页面",并触发app:error:clearedhook(见 clear-error 文档)。useError:返回当前正在处理的全局错误的响应式引用,可在自定义错误页或 middleware 中读取error.status、error.message等信息(error.ts)。<NuxtErrorBoundary>:客户端局部错误兜底组件,阻止错误冒泡到顶层、只渲染#error插槽,适合"不因局部错误毁掉整个页面"的场景(见 nuxt-error-boundary.md)。app/error.vue:全屏错误页的自定义入口,可通过 props 拿到NuxtError实例并使用clearError({ redirect: '/' })恢复(详见 error-handling 错误页章节)。
错误成因传递:用 cause 保留原始错误链
当你在 try/catch 中捕获底层异常后,往往不想丢失原始错误信息。createError 支持在创建错误时传入 cause 来保留被包装的原始错误:
try {
await fetchMovie(route.params.slug)
} catch (cause) {
throw createError({
status: 500,
message: 'Could not load movie',
cause,
})
}
cause 的行为在开发环境与生产环境存在严格差异(这是官方文档明确的安全边界):
- 开发环境:
cause链会通过错误页的cause属性暴露给你(自定义error.vue可通过 props 读取),序列化格式为{ name, message, stack, cause },形成可递归追踪的原始错误链。原始类型(primitive)的cause会原样透传;其他无法序列化的值会被省略。 - 生产环境:
cause永远不会出现在错误响应或错误页 payload 中,避免将底层堆栈与内部实现细节泄露给终端用户。
这一设计在 types.ts 的 SerializedErrorCause 类型中有明确体现。同时注意构造器中的回退取值链(error.ts):当错误详情自身没有提供 status/statusText/message 时,会依次向 cause 回退取值(details.status || cause?.status),这意味着包装一个已含状态码的错误时,内层错误的 HTTP 语义会被继承。
在 API 路由(Nitro 服务端)中使用:如何把错误正确传回客户端
createError 同样适用于 server/api/ 下的 Nitro 路由处理器,用于触发服务端错误处理:
export default eventHandler(() => {
throw createError({
status: 404,
statusText: 'Page Not Found',
})
})
此时 Nitro 会把该错误转换为对应的 HTTP 响应(如 404 + statusText)。文档给出了三条在 API 路由中的关键实践建议:
- 优先用简短
statusText:传对象时使用简短的statusText(如'Page Not Found'),因为statusText是少数能从服务端透传到客户端的字段;反之,在 API 路由里传给createError的message不会传播到客户端。 - 需要回传自定义数据就用
data:可以借助data属性把结构化业务数据返回给客户端。当前端使用useFetch处理该错误时,自定义数据位于error.value.data.data(第一层.data是 h3 错误 JSON 的data字段,第二层才是你传入的内容)。 - 警惕消息注入:无论如何,都应避免把动态用户输入拼进
message,防止潜在的安全问题(如日志注入 / 响应拆分)。
服务端与前端互通的底层实现:从 NuxtError 到 h3 的完整链路
将上文串联起来,可以看到 createError 是全栈错误链路的枢纽。从源码与测试可以还原出它的完整工作流:
- 你在页面/组件/API 路由中调用
createError并throw,得到一个name === 'HTTPError'、携带status/statusText/data的NuxtError实例。 - 若发生在 Nitro 服务端,error.test.ts 验证了
HTTPError.isError(error) === true——NuxtError 能被 h3 识别并映射成正确的 HTTP 状态码与响应头。 - 若发生在 SSR 渲染阶段,Nuxt 渲染管线识别出致命错误后,会渲染
app/error.vue(或在请求头接受 JSON 时返回 JSON 响应)。 - 错误进入 payload 后经由序列化(
toJSON(),见 error.ts,其 key 固定为['status', 'statusText', 'unhandled', 'message', 'data'])与反序列化还原,前端useError()/useFetch中的error.value即为一个可安全读取的NuxtError。 - 前端需要恢复时调用
clearError({ redirect }),重置错误状态、触发app:error:clearedhook,并按需路由跳转。
整个链条中,NuxtError 与 h3 的 HTTPError 在字段语义上的等价性由 error.test.ts 以一组等价输入做了逐字段断言(包括 status、statusText、message、data、body、unhandled 及 toJSON() 输出),这保证了"同一个 createError 对象在 Vue 端与 Nitro 端表现一致"这一全栈承诺是有测试兜底的。
小结与最佳实践清单
createError接受字符串(作为message,状态默认500)或对象;对象形态可携带status/statusText/message/fatal/cause/data/headers等元数据。- 服务端抛出触发全屏错误页(用
clearError恢复);客户端抛出默认非致命,需要全屏页须设fatal: true。 statusText只放简短短语(会被净化器过滤非法字符),详细描述一律进message;越界状态码会被钳制回500。- 用
cause保留原始错误链——开发环境可在错误页追溯{ name, message, stack, cause },生产环境一律不下发。 - API 路由中推荐
statusText或data回传结构化信息(useFetch下读取error.value.data.data),不要把动态用户输入拼进message。 - 更完整的全屏错误页定制、
vue:errorhook 与<NuxtErrorBoundary>局部兜底等场景,可继续阅读 Error Handling 章节、useError与showError的专项文档。
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 StartedRust0629
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python07
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00