網站開發過程與上正式環境後,難免會遇到一些 Bug 導致網站處理邏輯發生異常,甚至一小段的 JavaScript 的邏輯錯誤或未處理的拋出錯誤,可能導致整個 JavaScript 無法繼續執行,網站也跟著無法繼續互動,使用者全然不知道發生了什麼事情,如何的捕獲錯誤並給予適當的提示及引導,也是網站開發相當重要的一環。
當使用者前往一個不存在的頁面,例如前往 http://localhost:3000/omg 不存在頁面的網址,Nuxt 3 會為我們渲染一個 404 Page not found 的頁面。
而如果相同的頁面,你在 HTTP 請求中的夾帶 Accept: application/json
標頭,則回傳一個 JSON 的物件來描述錯誤訊息。
Nuxt 3 所預設的錯誤頁面可能不符合你的網站設計風格,你可以透過在專案的 pages 目錄下建立一個檔案 […slug].vue,來處理所有層級的頁面中不匹配的路由,來建立自訂的 404 Page not found 的頁面。
nuxt-app/
└── pages/
└── [...slug].vue
例如建立 ./pages/[…slug].vue 檔案
<template>
<div
style="background-image: linear-gradient(135deg, #0ea5e9 35%, #3b82f6 100%)"
class="flex min-h-screen items-center text-white"
>
<div class="mx-auto flex flex-wrap items-center p-4">
<div class="w-full p-4 text-center lg:w-1/2">
<div class="text-[12rem] font-semibold">404</div>
</div>
<div class="w-full p-4 text-center lg:w-1/2 lg:text-left">
<div class="mb-4 text-3xl font-medium">噢不!找不到這個頁面 😮</div>
<div class="mb-8 text-lg">您可能輸入了錯誤的網址或頁面已被刪除。</div>
<NuxtLink to="/" class="rounded border border-white px-4 py-2 hover:bg-gray-50 hover:bg-opacity-10 active:bg-opacity-20">回首頁</NuxtLink>
</div>
</div>
</div>
</template>
<script setup>
definePageMeta({
layout: false
})
</script>
當我們再次瀏覽不存在頁面的 http://localhost:3000/omg 網址,路由將會匹配至 […slug].vue 檔案所建立的頁面,也就實現自訂 404 Page not found 的錯誤頁面。
網站中在執行過程中,可能產生不同類型的錯誤,而當 Nuxt 3 遇到致命錯誤 (Fatal Error) 時,頁面就會像發生路由不匹配時會觸發的 404 Page not found 錯誤來顯示一個網頁進行提示,例如 about 頁面無法讀取為定義物件的 name 屬性,就會出現引發的 HTTP 狀態碼為 500。
如果我們想要自訂 Nuxt 3 遇到致命錯誤 (Fatal Error) 時的錯誤頁面,可以在專案根目錄下建立一個 error.vue 的檔案,來進行客製化。
nuxt-app/
├── ...
└── error.vue
例如建立 ./error.vue 檔案
<template>
<div
style="background-image: linear-gradient(135deg, #eab308 35%, #f59e0b 100%)"
class="flex min-h-screen items-center text-white"
>
<div class="mx-auto flex flex-wrap items-center p-4">
<div class="w-full p-4 text-center lg:w-1/2">
<div class="text-[12rem] font-semibold">{{ error.statusCode }}</div>
</div>
<div class="w-full p-4 text-center lg:w-1/2 lg:text-left">
<div class="mb-4 text-3xl font-medium">噢不!發生了一些意外 🙁</div>
<div class="mb-8 text-lg">{{ error.message }}</div>
<button
type="button"
class="rounded border border-white px-4 py-2 hover:bg-gray-50 hover:bg-opacity-10 active:bg-opacity-20"
@click="handleError"
>
清除錯誤後回到首頁
</button>
</div>
</div>
</div>
</template>
<script setup>
defineProps({
error: Object
})
const handleError = () => clearError({ redirect: '/' })
</script>
這個 error.vue 檔案,將會接收一個名為 error
的物件,致命錯誤的資訊包含在這個物件中,你可以從中取得拋出的 HTTP 狀態碼、錯誤訊息與錯誤堆疊 (Error stack),來根據你的需要顯示在自訂的錯誤頁面之中。
最後,通常我們會提供按鈕,可以觸發清理函式 clearError(),讓使用者可以清除目前的錯誤,並重新導向至 /
首頁或其他安全的頁面。
const handleError = () => clearError({ redirect: '/' })
如果沒有使用 clearError() 函式來清除錯誤,那麼某些情況 Nuxt 或 Vue App 將可能無法繼續正常的執行。
自訂錯誤頁面完成後,當我們再次瀏覽引發錯誤的網址,就會出現自訂的錯誤頁面。
此外,如果你的路由頁面沒有特別處理未匹配的路由頁面所呈現的自訂頁面,那麼 404 Page not found 的錯誤也會交由 error.vue 檔案來進行處理。
除了錯誤頁面的自訂外,更重要的是攔截記錄與解決錯誤的發生,Nuxt 3 是一個全端的框架 (Full-stack Framework),除了在客戶端 Vue 渲染生命週期中的錯誤外,同時也可能在伺服器端的 SSR 過程、Server API 或是 Nitro Engine 運作過程中發生錯誤,大致可以區分為下列四種:
不論在伺服器端渲染時期或客戶端執行時期 (SSR + SPA),Vue 的執行生命週期中所發生的錯誤,你都可以透過下列的方法,來捕獲這些錯誤。
你可以使用 Vue.js 所提供的函式 onErrorCaptured()
來註冊一個 Hook,用來捕獲 Vue 的子元件所拋出的錯誤。
舉例來說,我們建立一個按鈕元件 <OopsButton>
,並在點擊按鈕時拋出一個錯誤,模擬錯誤的發生。
<template>
<button
type="button"
class="inline-flex items-center rounded-md border border-transparent bg-orange-100 px-6 py-3 text-xl font-medium text-orange-700 hover:bg-orange-200 focus:outline-none focus:ring-2 focus:ring-orange-500 focus:ring-offset-2"
@click="onClick"
>
OOPS!
</button>
</template>
<script setup>
const onClick = () => {
throw new Error('由 OopsButton 元件,拋出的一個錯誤!')
}
</script>
接著我們可以在使用這個按鈕的元件中,使用 Vue 所提供的函式 onErrorCaptured()
來捕獲這個錯誤並接手處理。
<template>
<div class="flex flex-col items-center py-24">
<OopsButton />
<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
})
</script>
當你捕獲這個錯誤後就可以來做異常的處理,防止 Nuxt 或 Vue 可能因為發生異常,而導致無法繼續運作。
如果 Vue 中發生的錯誤沒有人接手處理,導致錯誤冒泡傳播到頂層,那我們可以透過 Vue 提供 errorHandler 來攔截。
首先,建立自訂的插件 ./plugins/vueErrorHandle.js。
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.vueApp.config.errorHandler = (error) => {
console.error('[由 vueErrorHandle 插件所捕獲的錯誤]', error)
}
})
接著我們在頁面中使用前述例子所建立的 <OopsButton>
,來模擬錯誤的發生。
<template>
<div class="flex flex-col items-center py-24">
<OopsButton />
</div>
</template>
除了可以使用 Vue 提供 errorHandler 來攔截錯誤外,Nuxt 提供了一個 vue:error
hook,將在這些冒泡傳播到頂層的錯誤進行捕獲,們可以自訂一個插件來整合錯誤處理或回報的框架來收集這些錯誤。
首先,建立自訂的插件 ./plugins/vueErrorHandle.js。
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.vueApp.config.errorHandler = (error) => {
console.error('[由 vueErrorHandle 插件所捕獲的錯誤]', error)
}
nuxtApp.hooks.hook('vue:error', (error) => {
console.error('[由 Nuxt 的 vue:error hook 所記錄到的錯誤]', error)
})
})
如果 Nuxt 在啟動時發生錯誤時,將會呼叫 Nuxt 的 app:error
hook,你可以用來處理下列幾種錯誤情況:
app:created
、app:beforeMount
或 app:mounted
hooks 處理時發生的錯誤。onErrorCaptured()
函式、errorHandler
或 Nuxt 的 vue:error
hook 進行處理。更多的 Nuxt Lifecycle Hooks 可以參考官方文件。
目前無法處理在伺服器端的 Server API 或 Nitro Engine 的生命週期中所發生錯誤,但可以這些錯誤將會被拋出你可以透過拋出的錯誤物件,來呈現自訂的錯誤頁面。
如果使用者在瀏覽網站時,由於網路環境的問題或是同時間網站有新的版本進行部署,那麼將會使舊的網站程式碼 JavaScript Chunk 檔案失效(因為會使用新的具有雜湊名稱的檔案),這時將使用者可能會遇到 Chunk 檔案的下載或載入錯誤。
Nuxt 3 的核心程式碼,內建了一個插件,可以在客戶端來處理 Chunk 發生載入錯誤時,可以強制執行重新載入新的 Chunk 檔案。
你也可以透過設定 Nuxt Config 的 experimental.emitRouteChunkError 選項,來調整發生 JavaScript Chunk 檔案載入錯誤時的處理方式,它的預設值為 automatic 將會自動重新載入;設定為 fasle 將會停用自動處理;或設定為 manual 來手動處理 Chunk 檔案載入的錯誤。
Nuxt 3 提供了一個 <NuxtErrorBoundary>
元件,可以來處理發生致命錯誤時,不觸發佔據整個視窗的錯誤頁面。
<NuxtErrorBoundary>
元件負責處理預設插槽內的子元件所發生的錯誤,在客戶端中,它也將阻止錯誤冒泡至頂層,#error
則是呈現錯誤的插槽,插槽會接收一個 error
屬性,如果將其設定為 null
就可以清除錯誤重新顯示預設插槽的內容。
<template>
<div class="flex flex-col items-center py-24">
<p class="my-8 text-2xl font-medium text-gray-700">測試 NuxtErrorBoundary 元件</p>
<NuxtErrorBoundary>
<OopsButton />
<template #error="{ error }">
<p class="my-4 text-xl text-gray-500">這裡是發生錯誤時,才會渲染出來的地方</p>
<button
type="button"
class="inline-flex items-center rounded-md border border-transparent bg-green-100 px-4 py-2 text-sm font-medium text-green-700 hover:bg-green-200 focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2"
@click="fixIssue(error)"
>
修正錯誤
</button>
</template>
</NuxtErrorBoundary>
</div>
</template>
<script setup>
const fixIssue = (error) => {
error.value = null
}
</script>
當觸發錯誤後將會顯示 #error
內容,透過提供的修正錯誤按鈕來觸發 fixIssue()
模擬修正好錯誤,就會再次重新顯示默認插槽的內容。
網站在瀏覽時難免存在著一些錯誤,無論是伺服器或客戶端的問題,能提供良好的錯誤資訊或錯誤頁面,才能讓使用者有比較好的使用者體驗,當然你也可以結合錯誤處理或記錄的框架來回收這些錯誤訊息進行後續的分析,但更重要的是背後的錯誤處理,Nuxt 3 提供了許多方便的組合式函數與 hook,我們可以結合這些方法盡量攔截錯誤並嘗試解決錯誤,讓使用者盡量不要有因為錯誤而放棄繼續使用網站的心力
感謝大家的閱讀,歡迎大家給予建議與討論,也請各位大大鞭小力一些:)
如果對這個 Nuxt 3 系列感興趣,可以訂閱
接收通知,也歡迎分享給喜歡或正在學習 Nuxt 3 的夥伴。
範例程式碼
參考資料