Skip to main content

error-handling

Nuxt 3 錯誤處理 (Error handling)

Nuxt 3 是一個全端的框架 (Full-stack Framework),除了在客戶端 Vue 渲染生命週期中的錯誤外,也包含了伺服器端的 SSR 過程、Server API 或是 Nitro Engine 運作過程中的錯誤,大致可以區分為下列三種:

  1. Vue 渲染生命週期中的錯誤 (SSR + SPA)
  2. 伺服器端 (Server) 和客戶端 (Client) 啟動時發生的錯誤 (SSR + SPA)
  3. API 或 Nitro Engine 伺服器端生命週期中的錯誤

Vue 渲染生命週期中的錯誤 (SSR + SPA)

onErrorCaptured

當我們啟用 SSR 時,也就是包含了在後端由 Vue 渲染生命週期中,發生的錯誤,可以使用 Vue.js 提供的 onErrorCaptured() 函數來註冊一個 hook,用以捕獲子元件所發生的錯誤。

舉例,新增一個 ButtonOOPS 元件,並添加一個點擊事件程式碼如下:

const onClick = () => {
throw new Error("由 ButtonOOPS 元件,拋出一個錯誤!");
};

使用 ButtonOOPS 按鈕元件,使用 onErrorCaptured() 來捕獲程式碼如下:

<!-- ./pages/vue/onErrorCaptured.vue -->
<template>
<div class="flex flex-col items-center bg-white py-24">
<ButtonOOPS />
<div class="mt-4 text-red-500">{{ errorMessage }}</div>
</div>
</template>

<script setup>
import { onErrorCaptured } from "vue";

const errorMessage = ref();

onErrorCaptured((err) => {
console.error("[捕獲錯誤]", err.message);
errorMessage.value = err.message;
return false; // 為了不要把 error 冒泡到上層
});
</script>

我們可以在 SFC 中使用 onErrorCaptured() 來捕獲子元件所發生的錯誤,當我們處理完錯誤可以使用 return false 來阻止錯誤冒泡 (Bubbling)。 https://i.imgur.com/4XRKv5L.gif

vueApp.config.errorHandler

Nuxt 提供了一個 vue:error 的 hook,如果 Vue 中發生的錯誤冒泡傳播到頂層,就會呼叫這個 hook,若有使用錯誤處理或回報的框架,我們可以自訂一個插件來接收 Vue 所有的錯誤。這個算是個 global 的 error catch

// ./plugins/vueErrorHandle.js
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.vueApp.config.errorHandler = (error) => {
console.error("[由 vueErrorHandle 插件捕獲的錯誤]", error);
};
});

建立一個頁面 ./pages/vue/thowError.vue 來使用 ButtonOOPS 按鈕元件程式碼如下,但不捕獲錯誤,將錯誤繼續冒泡:

<template>
<div class="flex flex-col items-center bg-white py-24">
<ButtonOOPS />
</div>
</template>

元件中所發生的錯誤,不論是否有被處理,只要錯誤冒泡至頂層,就會被 vueErrorHandle 插件所定義的 vue:error 的 hook 所捕獲。 https://i.imgur.com/xCBhUbs.gif

伺服器端 (Server) 和客戶端 (Client) 啟動時發生的錯誤 (SSR + SPA)

如果 Nuxt 在啟動時發生錯誤時,將會呼叫 Nuxt 實例的 app:error hook,你可以用來處理下列幾種錯誤情況:

  • 執行 Nuxt 插件時
  • app:createdapp:beforeMountapp:mounted hooks 處理時發生的錯誤。
  • 在客戶端 Mounting Vue App。但建議使用 Vue.js 的 onErrorCaptured 或 Nuxt 的 vue:error hook 進行處理。

更多的 Lifecycle Hooks 可以參考官方文件

API 或 Nitro Engine 伺服器端生命週期中的錯誤

目前無法在伺服器端定義 Server API 或 Nitro Engine 的生命週期中發生錯誤時的錯誤處理,但可以呈現錯誤的頁面。

Nuxt 3 呈現錯誤頁面

當 Nuxt 遇到致命的錯誤時,不論是在伺服器生命週期期間,抑或在前端或後端渲染 Vue App 時,它都會渲染一個 HTML 錯誤頁面。

例如,我們前往一個不存在的頁面 http://localhost:3000/omg,瀏覽器將會渲染出一個 Nuxt 預設的錯誤頁面。 https://ithelp.ithome.com.tw/upload/images/20221012/20152617Dpa9DIxgLn.png

而當 HTTP 請求帶有 Accept: application/json 標頭,則回傳一個 JSON 的物件來描述錯誤訊息。 https://ithelp.ithome.com.tw/upload/images/20221012/201526170Dl3tqmYwf.png

當然,如果你還記得頁面 (Pages) 與路由 (Routing)的設定,我們可以自己建立一個 ./pages/404.vue 來處理找不到路由的錯誤頁面。不過呢,在 Nuxt 與 Vue App 中可能發生的錯誤會更多,所以會由其他頁面進行錯誤頁面的渲染。

Nuxt 3 自訂錯誤頁面

Nuxt 預設的錯誤頁面,我們可以在專案目錄下建立 error.vue,注意這個頁面檔案與 app.vue 同層級,專案目錄下其他檔案就先不列,同層級意思就是結構大概長的像下面這樣。

nuxt-app/ ├── pages.vue └── error.vue

error.vue 同時也具有一個 error 的屬性 (Props),提供我們進行處理。

<script setup>
const props = defineProps({
error: Object,
});
</script>

當定義好自訂的錯誤頁面,我們可以呼叫 clearError() 函數來清除錯誤並重導向至首頁 / 或其他安全的頁面。

<template>
<div class="flex flex-col items-center bg-white py-24">
<h1 class="my-12 text-5xl font-semibold text-red-500">發生了錯誤!</h1>
<div class="flex flex-col items-center justify-center">
<p class="text-xl text-gray-500">{{ error.message }}</p>
<button
type="button"
class="mt-8 block rounded-md border border-transparent bg-gray-100 px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-200 focus:outline-none focus:ring-2 focus:ring-gray-500 focus:ring-offset-2"
@click="handleError"
>
清除錯誤後回到首頁
</button>
</div>
</div>
</template>

<script setup>
defineProps({
error: Object,
});

const handleError = () => clearError({ redirect: "/" });
</script>

這邊也要特別注意使用 clearError() 函數來清除錯誤,在使用 Nuxt 任何插件,例如 Vue Router,我們的頁面發生了錯誤後一定要檢查並處理,如果沒有清除錯誤,那麼 Vue App 將不會重新正常的執行。

搭配布局自訂錯誤頁面

建立一個 ./layouts/error.vue 布局,程式碼如下:

<template>
<div class="flex flex-col items-center bg-white py-24">
<h1 class="my-6 text-5xl font-semibold text-red-500">發生了錯誤!</h1>
<slot />
</div>
</template>

調整 ./error.vue 內容如下,注意這邊需要使用 definePageMeta({ layout: false }) 取得布局的完整控制權後,搭配 <NuxtLayout name="error">

<template>
<NuxtLayout name="error">
<div class="flex flex-col items-center justify-center">
<p class="text-xl text-gray-500">{{ error.message }}</p>
<button
type="button"
class="mt-8 block rounded-md border border-transparent bg-gray-100 px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-200 focus:outline-none focus:ring-2 focus:ring-gray-500 focus:ring-offset-2"
@click="handleError"
>
清除錯誤後回到首頁
</button>
</div>
</NuxtLayout>
</template>

<script setup>
defineProps({
error: Object,
});

const handleError = () => clearError({ redirect: "/" });

definePageMeta({
layout: false,
});
</script>

如此一來我們也能在 error.vue 頁面中使用自訂義的 error 布局。

因為專案有搭配 Tailwind CSS,記得也要把 './error.{js,ts,vue}' 添加至 tailwind.config.js 設定檔的 content 屬性中,才能正確的套用樣式類型。

Nuxt 3 錯誤處理的輔助函數

useError

參數類型:

function useError(): Ref<Error | { url; statusCode; statusMessage; message; description; data }>;

此方法將回傳 Nuxt 正在處理的全域錯誤。

使用範例:

const error = useError();

clearError

參數類型:

function clearError(options?: { redirect?: string }): Promise<void>;

此函數將清除目前 Nuxt 處理中的錯誤,函數可以傳入一個路徑來進行重導向,例如清除錯誤後,可以導向至首頁或其他安全的頁面。

使用範例:

clearError();

// or

clearError({ redirect: "/safe" });

createError

參數類型:

function createError(err: { cause; data; message; name; stack; statusCode; statusMessage; fatal }): Error;

此函數可以建立一個帶有附加錯誤資訊的物件,它可以在 Vue 或 Nitro 中使用,並且可以使用 throw 拋出這個錯誤物件。

如果我們使用 createError 建立一個錯誤物件,在伺服器端或客戶端會有不同的效果。

伺服器端

在伺服器端拋出 createError() 建立的錯誤物件時,它將觸發一個全螢幕的錯誤,你可以使用 clearError() 來清除這個錯誤。

使用範例:

<script setup>
throw createError({
statusCode: 500,
statusMessage: "Internal Server Error",
});
</script>

https://ithelp.ithome.com.tw/upload/images/20221012/20152617KgY3VvYdh8.png

客戶端

若是在客戶端拋出 createError() 建立的錯誤物件,它將會拋出一個非致命的錯誤提供處理,如果想觸發全螢幕的錯誤頁面,可以設定 fatal 屬性為 true

使用範例:

<script setup>
import { onErrorCaptured } from "vue";

onErrorCaptured((err) => {
console.error("[捕獲錯誤]", err.message);

throw createError({
statusCode: 400,
statusMessage: "Bad Request",
fatal: true,
});
});
</script>

https://i.imgur.com/RLcUZot.gif

showError

參數類型:

function showError(err: string | Error | { statusCode; statusMessage }): Error;

你可以在客戶端的任何地方呼叫此函數,伺服器端則可以在中間件、插件或 setup() 函數中呼叫。它將觸發一個全螢幕的錯誤頁面,你可以使用 clearError() 來清除錯誤。

因為錯誤頁面與 createError() 觸發的頁面相同,官方也建議改為使用 throw createError() 來建立與拋出錯誤。

使用範例:

showError("? Oh no, an error has been thrown.");
showError({ statusCode: 404, statusMessage: "Page Not Found" });

參考文章

https://ithelp.ithome.com.tw/articles/10308008