前言
前端异常处理,终极目的在于,万一应用哪个地方抛出了异常,也能用兜底的数据显示兜底的UI。如果结合异常上报监控平台,还能及时发现bug,并在bug造成一定影响之前默默解决上线。因此,异常处理做好了,可以极大降低白屏和错误页面的出现概率,极大降低线上事故率,还能极大提升用户体验。
下面就Nuxt框架常见的异常处理方案,做个介绍。注意低版本Nuxt的异常处理部分,有bug,如果想要有较好的异常处理体验,建议升级到3.10.0版本以上。
Nuxt异常处理相关知识
Nuxt常见的异常捕获方式:
onErrorCaptured
当从子组件异常向上抛出被捕获时触发。
如果不想继续透传错误,可以return false,否则会继续透传到应用级别的错误处理app.config.errorHandler
。
function onErrorCaptured(callback: ErrorCapturedHook): void
type ErrorCapturedHook = (
err: unknown,
instance: ComponentPublicInstance | null,
info: string
) => boolean | void
可以捕获的异常有:
- Component renders
- Event handlers
- Lifecycle hooks
setup()
function- Watchers
- Custom directive hooks
- Transition hooks
vue:error
vue异常。
Called when a vue error propagates to the root component, including Server & Client
app:error
应用级别的异常。fatal类异常触发。
可以捕获的异常有:
- running Nuxt plugins
- processing
app:created
andapp:beforeMount
hooks - rendering your Vue app to HTML (during SSR)
- mounting the app (on client-side), though you should handle this case with
onErrorCaptured
or withvue:error
- processing the
app:mounted
hook
vueApp.config.errorHandler
应用级的错误捕获方法。
interface AppConfig {
errorHandler?: (
err: unknown,
instance: ComponentPublicInstance | null,
// `info` is a Vue-specific error info,
// e.g. which lifecycle hook the error was thrown in
info: string
) => void
}
app.config.errorHandler = (err, instance, info) => {
// handle error, e.g. report to a service
}
可以捕获的异常有:
- Component renders
- Event handlers
- Lifecycle hooks
setup()
function- Watchers
- Custom directive hooks
- Transition hooks
Nuxt常见的几种运行时异常:
Vue Errors
vue渲染生命周期阶段异常(SSR&CSR)
可以使用以下三种方式捕获处理:
-
onErrorCaptured
- 如果不想继续透传错误,可以return false,否则会继续透传到应用级别的错误处理app.
config.errorHandler
。
- 如果不想继续透传错误,可以return false,否则会继续透传到应用级别的错误处理app.
-
vue:error
Called when a vue error propagates to the root component, including Server & Client。
-
vueApp.config.errorHandler
-
应用级的错误捕获方法。可以捕获所有vue错误,即便已被处理。注意如果ErrorCapture返回false是不会到此的。
-
示例如下:
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.vueApp.config.errorHandler = (error, instance, info) => {
// handle error, e.g. report to a service
// return false to prevent the error from propagating further.
// return false
}
// Also possible
nuxtApp.hook('vue:error', (error, instance, info) => {
// handle error, e.g. report to a service
})
})
Startup Errors
服务端和客户端启动异常(SSR&CSR)
当启动Nuxt应用时碰到任何异常,Nuxt会自行调用 app:error
hooks。
会监听包括:
-
Nuxt plugins执行异常
-
app:created
和app:beforeMount
执行异常 -
SSR阶段将Vue 转换成HTML阶段的异常
-
客户端渲染异常:也可自己使用
onErrorCaptured
or withvue:error
处理异常。也可使用<NuxtErrorBoundary>
处理 -
app:mounted
执行异常
Js Chunks Errors
Nuxt会自动硬重加载。
Nitro Server Errors
服务端生命周期内异常(server/文件夹)
目前无法处理此类异常,但可以使用结合Error Page处理该类异常。
跟单站没用此文件夹,可忽略
Error常见的处理方式
总所周知,Vue的工作原理是将界面的各个部分抽象成组件,并通过虚拟DOM和响应式系统等技术实现了数据驱动视图的效果。下面就从视图UI和数据两个角度来展开异常处理。
异常处理-UI视图
实现有异常能捕获,并需要时展示兜底UI,同时做异常处理如兜底数据及异常上报等。
全局Error Page
全局默认的全屏错误页。当Nuxt碰到致命错误的时候(包括服务端未处理的错误和客户端fatal: true类型的错误),默认会跳转到全屏的错误页面,默认500页面,或者当请求类型是Accept: application/json的时候会返回JSON。
常见的服务端和客户端错误
服务端:
- Nuxt plugin执行错误
- Vue app 转 HTML
- 服务端API执行错误
客户端:
-
Nuxt plugin执行错误
-
应用mount前错误:
app:beforeMount
hook -
应用mount时错误(若没经
onErrorCaptured
orvue:error
hook处理,会抛出) -
应用初始化和在浏览器mounted后的错误:
app:mounted
默认的全屏错误页面:
路径:~/error.vue
注意:该错误页面,可以使用clearError
清掉,可以自行按需清掉跳转安全页面如首页等。
TODO:这里可以尝试优化下,当出现全屏错误页时,是否有必要跳转到首页。
<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.statusCode }}</h2>
<button @click="handleError">Clear errors</button>
</div>
</template>
异常边界-ErrorBoundary
layout和Page级别的ErrorBoundary:
可以做layout和Page级别的ErrorBoundary处理页面整体的异常和兜底UI。(Nuxt框架在3.10.0以下版本有bug,如果在水合阶段page or layout出现异常,则Nuxt不会clear Error,表现为切换路由时仍然维持当前错误。)
组件级别的ErrorBoundary:排行榜 List
也可以精细化做组件的ErrorBoundary,兜底组件的异常,阻止错误向上抛出,触发全屏的错误页面,提升用户体验
<NuxtErrorBoundary>
针对客户端的异常,可以使用<NuxtErrorBoundary>
进行异常处理,和兜底UI。
工作原理:底层使用 onErrorCaptured
hook。当默认slot出现异常,就会渲染#error
,而不会触发全局的Error Page.
事件:@error
插槽:#error
,会接收error属性,当error=null会触发程序渲染default slot.
<template>
<!-- some content -->
<NuxtErrorBoundary @error="someErrorLogger">
<!-- You use the default slot to render your content -->
<template #error="{ error, clearError }">
You can display the error locally here: {{ error }}
<button @click="clearError">
This will clear the error.
</button>
</template>
</NuxtErrorBoundary>
</template>
<NuxtClientFallback>
📢目前不推荐使用,github上有吐槽很消耗内存。具体参考:github.com/nuxt/nuxt/i…
<NuxtClientFallback>
组件用来处理,当子组件在SSR阶段出错时,父组件在客户端渲染的兜底UI。为实验新特性。
<template>
<div>
<Sidebar />
<!-- this component will be rendered on client-side -->
<NuxtClientFallback fallback-tag="span" @ssr-error="logSomeError">
<Comments />
<BrokeInSSR />
<template #fallback>
<!-- this will be rendered on server side if the default slot fails to render in ssr -->
<p>Hello world</p>
</template>
</NuxtClientFallback>
</div>
</template>
注意:此特性目前为实验特性。需要在config中打开实验开关
export default defineNuxtConfig({
experimental: {
clientFallback: true
}
})
异常处理-数据状态
使用Nuxt/Vue框架提供的hooks和方法处理异常:
详细参考上面Nuxt常见的异常捕获方式,按需使用。
onErrorCaptured
composable that can be called in a page/component setup functionvue:error
: runtime nuxt hook that can be configured in a nuxt plugin,will be called if any errors propagate up to the top level.app:error
:处理应用级别的异常vueApp.config.errorHandler
:会接收所有l Vue errors, 即便已经被处理。可以在此做异常上报,比如Sentry上报。
export default defineNuxtPlugin(nuxtApp => {
nuxtApp.hook('vue:error', (err) => {
//
})
})
开发过程中自主捕获处理异常
包括try catch, promise catch等捕获有嫌疑的代码段的异常,并在有必要的时候处理兜底数据。
具体不介绍了,参考下面之前的文档。这里各个JS框架都是类似的。
参考文档:# 前端异常处理-常见处理方式
异常处理落地方案
直接说落地的结论,具体Nuxt常见异常及处理方案参看下面的介绍。
全局处理:
-
针对Client端异常:
- 最终采用的方案是,自定义ErrorBoundary组件处理和兜底异常,使用在Component组件级别。layout和page级别因为官方异常处理还有bug,所以使用效果不符合预期,体验上不如不用,所以nuxt升级之前不推荐使用。
- 自定义
<NuxtErrorBoundary>
处理异常和兜底UI。这里可以分阶段,先处理全局layout和Page,然后再精细化处理关键组件模块。(注意这个不能处理SSR阶段的异常!)-> 经测试官方提供的<NuxtErrorBoundary>
有坑,依然会触发全局的500页面,具体见github.com/nuxt/nuxt/i… - 自定义ErrorBoundary,经测试,clearError在切换路由的时候不会触发,错误页面依然会保持。这个也是nuxt的一个bug,具体见 github.com/nuxt/nuxt/p….
- 因此ErrorBoundary不能用于layout和page异常处理。只能在component上先用起来。目前在排行榜页面的List组件首先尝试使用。观察效果,如果不错,后续在其它重点组件推广。
-
针对Sever异常:
- 最终采用的方案是,封装error plugin捕获异常,从而阻止继续向上透传错误,而不展示全屏的500页面,显示已经渲染的业务页面。体验上会更好。
- 针对子组件SSR异常: 父组件使用
<NuxtClientFallback>
处理子组件SSR异常和兜底UI (不推荐,很耗内存,详见:github.com/nuxt/nuxt/i… - Error plugin可以捕获,不继续向上透传错误,从而不展示500页面。但这里有个问题,刷新之后还是会500。而且nuxt3.10.0以下有个bug,500之后错误会保持而不会被清理。这个需要升级解决。
- Try catch报错可疑代码块,捕获处理异常
-
自定义异常处理plugins捕获和处理异常
-
经实验,这里捕获处理之后,尤其是服务端异常,捕获之后可处理不继续向上透传错误,从而不触发全局的500页面error.vue,而显示已经显示的UI。按需用,用户体验会更好。
-
vue:error
- Called when a vue error propagates to the root component, Server & Client
-
app:error
- Called when a fatal error occurs, Server & Client
-
vueApp.config.errorHandler
- 全局异常捕获方法。Assign a global handler for uncaught errors propagating from within the application
-
-
全局封装error.vue自定义全屏错误页面:优化错误UI,引导用户跳转或clearError
局部处理:
- ErrorBoundary兜底重要组件
- 可能出错的代码段,尤其异步代码段,做异常捕获处理。如用try catch包裹捕获异常并处理。Promise的异常处理。正则及JSON方法的异常处理等。
- 精细化处理关键组件模块的异常,并做UI兜底处理。
开发过程中发现的问题:
⚠️ 注意。实际调研和落地使用过程中深有体会,Nuxt生态较为不成熟,异常处理体系非常不完善,官方相关方案少而且有不少坑、支持少,网上相关资源也少。并且,官方提供的工具还有坑,而且官网文档描述和实际使用中表现不一致。
- 坑1: onMounted里error触发ErrorBoundary的表现不一致:按官网,预期当onMounted里抛出error会触发ErrorBoundary显示兜底UI。但实际测试中,场景不同,表现也不同。具体表现为:例如本地build和测试环境build不一致;本地测试服务不同页面表现也不一致。不一致体现在,同样是在onMounted里throw error, 有的还是会触发全局的500页面,有的会展现ErrorBoundary UI, 有的会呈现原页面只是不全。前者符合官网描述预期,而后者明显体验更优,但后者出现的场景原因不明。表现不一致,这样测试效果就比较棘手。
网上查了下,果然有这个坑:github.com/nuxt/nuxt/i…
- 坑2: 切换路由,错误页面都会显示ErrorBoundary,刷新则正常显示页面但控制台有error handle。并且切换路由,不会像官网说的会自动clearError,错误会一直存在。坑: github.com/nuxt/nuxt/p….
-
坑3: 官方推荐的
<NuxtClientFallback>
,非常消耗内存。github.com/nuxt/nuxt/i… -
server阶段的错误目前会显示全屏500也即error.vue,本地dev环境会展示自定义500页面,但是测试环境不会显示自定义500页面。这个后续自定义500页面接入的时候需要看下,也可能升级之后就不存在这个问题了。
参考:
Nuxt hooks: nuxt.com/docs/api/ad…