Nuxt异常处理方案

232 阅读10分钟

前言

前端异常处理,终极目的在于,万一应用哪个地方抛出了异常,也能用兜底的数据显示兜底的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 and app: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 with vue: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)

可以使用以下三种方式捕获处理:

  1. onErrorCaptured

    1.   如果不想继续透传错误,可以return false,否则会继续透传到应用级别的错误处理app.config.errorHandler
  2. vue:error

Called when a vue error propagates to the root component, including Server & Client。

  1. vueApp.config.errorHandler

    1.   应用级的错误捕获方法。可以捕获所有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。

会监听包括:

  1. Nuxt plugins执行异常

  2. app:createdapp:beforeMount执行异常

  3. SSR阶段将Vue 转换成HTML阶段的异常

  4. 客户端渲染异常:也可自己使用onErrorCaptured or with vue:error处理异常。也可使用<NuxtErrorBoundary>处理

  5. 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。

常见的服务端和客户端错误

服务端:

  1. Nuxt plugin执行错误
  2. Vue app 转 HTML
  3. 服务端API执行错误

客户端:

  1. Nuxt plugin执行错误

  2. 应用mount前错误:app:beforeMount hook

  3. 应用mount时错误(若没经 onErrorCaptured or vue:error hook处理,会抛出)

  4. 应用初始化和在浏览器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常见的异常捕获方式,按需使用。

  1. onErrorCaptured composable that can be called in a page/component setup function
  2. vue:error : runtime nuxt hook that can be configured in a nuxt plugin,will be called if any errors propagate up to the top level.
  3. app:error :处理应用级别的异常
  4. vueApp.config.errorHandler:会接收所有l Vue errors, 即便已经被处理。可以在此做异常上报,比如Sentry上报。
export default defineNuxtPlugin(nuxtApp => {
  nuxtApp.hook('vue:error', (err) => {
    //
  })
})

开发过程中自主捕获处理异常

包括try catch, promise catch等捕获有嫌疑的代码段的异常,并在有必要的时候处理兜底数据。

具体不介绍了,参考下面之前的文档。这里各个JS框架都是类似的。

参考文档:# 前端异常处理-常见处理方式

异常处理落地方案

直接说落地的结论,具体Nuxt常见异常及处理方案参看下面的介绍。

全局处理:

  1. 针对Client端异常:

    1.   最终采用的方案是,自定义ErrorBoundary组件处理和兜底异常,使用在Component组件级别。layout和page级别因为官方异常处理还有bug,所以使用效果不符合预期,体验上不如不用,所以nuxt升级之前不推荐使用。
    2.   自定义<NuxtErrorBoundary> 处理异常和兜底UI。这里可以分阶段,先处理全局layout和Page,然后再精细化处理关键组件模块。(注意这个不能处理SSR阶段的异常!)-> 经测试官方提供的<NuxtErrorBoundary>有坑,依然会触发全局的500页面,具体见github.com/nuxt/nuxt/i…
    3.   自定义ErrorBoundary,经测试,clearError在切换路由的时候不会触发,错误页面依然会保持。这个也是nuxt的一个bug,具体见 github.com/nuxt/nuxt/p….
    4.   因此ErrorBoundary不能用于layout和page异常处理。只能在component上先用起来。目前在排行榜页面的List组件首先尝试使用。观察效果,如果不错,后续在其它重点组件推广。
  2. 针对Sever异常:

    1.   最终采用的方案是,封装error plugin捕获异常,从而阻止继续向上透传错误,而不展示全屏的500页面,显示已经渲染的业务页面。体验上会更好。
    2. 针对子组件SSR异常: 父组件使用<NuxtClientFallback> 处理子组件SSR异常和兜底UI (不推荐,很耗内存,详见:github.com/nuxt/nuxt/i…
    3. Error plugin可以捕获,不继续向上透传错误,从而不展示500页面。但这里有个问题,刷新之后还是会500。而且nuxt3.10.0以下有个bug,500之后错误会保持而不会被清理。这个需要升级解决。
    4. Try catch报错可疑代码块,捕获处理异常
  3. 自定义异常处理plugins捕获和处理异常

    1.   经实验,这里捕获处理之后,尤其是服务端异常,捕获之后可处理不继续向上透传错误,从而不触发全局的500页面error.vue,而显示已经显示的UI。按需用,用户体验会更好。

    2. vue:error

      1.     Called when a vue error propagates to the root component, Server & Client
    3. app:error

      1.     Called when a fatal error occurs, Server & Client
    4. vueApp.config.errorHandler

      1.     全局异常捕获方法。Assign a global handler for uncaught errors propagating from within the application
  4. 全局封装error.vue自定义全屏错误页面:优化错误UI,引导用户跳转或clearError

局部处理:

  1. ErrorBoundary兜底重要组件
  2. 可能出错的代码段,尤其异步代码段,做异常捕获处理。如用try catch包裹捕获异常并处理。Promise的异常处理。正则及JSON方法的异常处理等。
  3. 精细化处理关键组件模块的异常,并做UI兜底处理。

开发过程中发现的问题:

⚠️ 注意。实际调研和落地使用过程中深有体会,Nuxt生态较为不成熟,异常处理体系非常不完善,官方相关方案少而且有不少坑、支持少,网上相关资源也少。并且,官方提供的工具还有坑,而且官网文档描述和实际使用中表现不一致。

  1. 坑1: onMounted里error触发ErrorBoundary的表现不一致:按官网,预期当onMounted里抛出error会触发ErrorBoundary显示兜底UI。但实际测试中,场景不同,表现也不同。具体表现为:例如本地build和测试环境build不一致;本地测试服务不同页面表现也不一致。不一致体现在,同样是在onMounted里throw error, 有的还是会触发全局的500页面,有的会展现ErrorBoundary UI, 有的会呈现原页面只是不全。前者符合官网描述预期,而后者明显体验更优,但后者出现的场景原因不明。表现不一致,这样测试效果就比较棘手。

网上查了下,果然有这个坑:github.com/nuxt/nuxt/i…

  1. 坑2: 切换路由,错误页面都会显示ErrorBoundary,刷新则正常显示页面但控制台有error handle。并且切换路由,不会像官网说的会自动clearError,错误会一直存在。坑: github.com/nuxt/nuxt/p….

  1. 坑3: 官方推荐的<NuxtClientFallback>,非常消耗内存。github.com/nuxt/nuxt/i…

  2. server阶段的错误目前会显示全屏500也即error.vue,本地dev环境会展示自定义500页面,但是测试环境不会显示自定义500页面。这个后续自定义500页面接入的时候需要看下,也可能升级之后就不存在这个问题了。

参考:

Nuxt hooks: nuxt.com/docs/api/ad…