错误处理

Nuxt 是全栈框架,错误可能来自四个方向:Vue 渲染、应用启动、Nitro 服务端、JS chunk 下载。每一种都有对应的处理工具。

Vue 错误

在组件内用 onErrorCaptured 捕获;全局一次性注册用插件:

// app/plugins/error-report.ts
export default defineNuxtPlugin((nuxtApp) => {
  nuxtApp.vueApp.config.errorHandler = (error, instance, info) => {
    // 上报到 Sentry / Datadog / 自己的后端
  }

  nuxtApp.hook('vue:error', (error, instance, info) => {
    // 即使被处理过的错误也会触发
  })
})

vue:error 就是 Nuxt 版的 onErrorCaptured —— 所有冒泡到顶层的错误都会过这里。

启动错误

插件初始化、SSR、客户端挂载阶段的错误会触发 app:error。可在这里做埋点、清缓存、重置状态。

Nitro 服务端错误

服务端错误不会被 Vue 边界捕获,会直接进入错误页面。要自定义响应,在 event handler 里 throw createError({...})

// server/api/posts/[id].get.ts
export default defineEventHandler((event) => {
  const post = findPost(event.context.params!.id)
  if (!post) {
    throw createError({ status: 404, statusText: 'Post not found' })
  }
  return post
})

Chunk 加载失败

用户停留在旧页面、你又重新部署时,旧的 chunk URL 可能 404。Nuxt 默认会触发硬刷新。想自定义,把 experimental.emitRouteChunkError 设为 'manual',然后自己监听 nuxt:chunk-error

error.vue —— 兜底页面

触发致命错误(未捕获的服务端错误、或客户端 createError({ fatal: true }))时,Nuxt 会渲染 ~/error.vue

<!-- error.vue -->
<script setup lang="ts">
import type { NuxtError } from '#app'

const props = defineProps<{ error: NuxtError }>()

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

<template>
  <section>
    <h1>{{ error.statusCode }}</h1>
    <p>{{ error.statusMessage }}</p>
    <button @click="handleError">回首页</button>
  </section>
</template>

几个细节:

  • error.vue 是一次单独的页面渲染,路由中间件会重新跑。可以用 useError() 判断“当前是不是错误页”。
  • Accept: application/json 的请求不会拿到 HTML,而是 JSON 响应。

抛错:createError

import { createError } from '#app'

if (!data.value) {
  throw createError({
    status: 404,
    statusText: 'Page Not Found',
  })
}
  • 服务端(setup / 插件 / handler)抛出,会直接进错误页面。
  • 客户端默认是非致命的;要进错误页,加 { fatal: true }

statusText 请保持简短、仅 ASCII。长描述或国际化文案放到 messagedata 里。

showErrorclearError

  • showError(err):命令式触发错误页面。
  • clearError({ redirect: '/' }):清掉当前错误,可选跳转。

setup / handler 里优先 throw createError(...);已经 catch 住再手动暴露时再用 showError

NuxtErrorBoundary —— 局部错误 UI

不要因为一个组件挂了就崩整页,包一层:

<template>
  <NuxtErrorBoundary @error="report">
    <RemoteChart />
    <template #error="{ error, clearError }">
      <p>图表加载失败。</p>
      <button @click="clearError">重试</button>
    </template>
  </NuxtErrorBoundary>
</template>

<script setup lang="ts">
function report (err: unknown) { /* 上报 */ }
</script>

error = null 会重新渲染默认 slot,所以要先解决根因,否则 slot 立刻再抛。

useError() —— 读取当前错误

在中间件、插件、布局里都能读:

const error = useError()
if (error.value) {
  // 正处于错误页流程
}

用来跳过登录检查、隐藏外壳都很方便。

Checklist

  • 插件里注册全局 handler(errorHandler + vue:error)。
  • 提供 error.vue,给用户一条友好的恢复路径。
  • Nitro handler 用 createError 抛 4xx / 5xx。
  • 高风险组件包 NuxtErrorBoundary
  • 上报 chunk-error 指标,及时发现发版导致的会话中断。

好的错误体验本身就是一个功能,认真对待。

錯誤處理

Nuxt 是全棧框架,錯誤來源可能有四處:Vue 渲染、應用啟動、Nitro 伺服器、JS chunk 下載。各自有對應的工具。

Vue 錯誤

於元件內以 onErrorCaptured 捕捉;全域一次性註冊請用外掛:

// app/plugins/error-report.ts
export default defineNuxtPlugin((nuxtApp) => {
  nuxtApp.vueApp.config.errorHandler = (error, instance, info) => {
    // 回報至 Sentry / Datadog / 自家後端
  }

  nuxtApp.hook('vue:error', (error, instance, info) => {
    // 即便已被處理的錯誤亦會觸發
  })
})

vue:error 即為 Nuxt 版的 onErrorCaptured —— 冒泡至頂層的錯誤皆會經此。

啟動錯誤

外掛初始化、SSR、客戶端掛載階段的錯誤會觸發 app:error。可於此埋點、清快取、重設狀態。

Nitro 伺服器錯誤

伺服器錯誤不會被 Vue 邊界捕捉,會直接進入錯誤頁面。若需自訂回應,於 event handler 內 throw createError({...})

// server/api/posts/[id].get.ts
export default defineEventHandler((event) => {
  const post = findPost(event.context.params!.id)
  if (!post) {
    throw createError({ status: 404, statusText: 'Post not found' })
  }
  return post
})

Chunk 載入失敗

使用者停留於舊頁、你再部署新版本時,舊 chunk URL 可能 404。Nuxt 預設會觸發強制重新整理。如需自訂,將 experimental.emitRouteChunkError 設為 'manual',再自行監聽 nuxt:chunk-error

error.vue —— 兜底頁面

觸發致命錯誤(未捕捉的伺服器錯誤、或客戶端 createError({ fatal: true }))時,Nuxt 會渲染 ~/error.vue

<!-- error.vue -->
<script setup lang="ts">
import type { NuxtError } from '#app'

const props = defineProps<{ error: NuxtError }>()

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

<template>
  <section>
    <h1>{{ error.statusCode }}</h1>
    <p>{{ error.statusMessage }}</p>
    <button @click="handleError">回首頁</button>
  </section>
</template>

幾個細節:

  • error.vue獨立頁面渲染,路由中介層會重新執行。可用 useError() 判斷當前是否處於錯誤頁流程。
  • Accept: application/json 的請求會收到 JSON 回應,而非 HTML 錯誤頁。

拋錯:createError

import { createError } from '#app'

if (!data.value) {
  throw createError({
    status: 404,
    statusText: 'Page Not Found',
  })
}
  • 伺服器端(setup/外掛/handler)拋出,會直接進入錯誤頁。
  • 客戶端預設為非致命;若要進錯誤頁,加 { fatal: true }

statusText 請保持簡短、僅 ASCII。長描述或國際化文案請放 messagedata

showErrorclearError

  • showError(err):命令式觸發錯誤頁。
  • clearError({ redirect: '/' }):清除當前錯誤,可選擇跳轉。

setup/handler 內優先 throw createError(...);已捕捉後再手動呈現時才用 showError

NuxtErrorBoundary —— 局部錯誤 UI

勿因單一元件故障致整頁崩潰,可包裹:

<template>
  <NuxtErrorBoundary @error="report">
    <RemoteChart />
    <template #error="{ error, clearError }">
      <p>圖表載入失敗。</p>
      <button @click="clearError">重試</button>
    </template>
  </NuxtErrorBoundary>
</template>

<script setup lang="ts">
function report (err: unknown) { /* 回報 */ }
</script>

error = null 會重新渲染預設 slot,請先解決根因,否則 slot 會立即再度拋出。

useError() —— 讀取當前錯誤

於中介層、外掛、佈局中皆可讀取:

const error = useError()
if (error.value) {
  // 正處於錯誤頁流程
}

可用於跳過登入檢查、隱藏外殼。

Checklist

  • 於外掛註冊全域 handler(errorHandler + vue:error)。
  • 提供 error.vue,給使用者友善的復原路徑。
  • Nitro handler 以 createError 拋出 4xx/5xx。
  • 高風險元件包 NuxtErrorBoundary
  • 監控 chunk-error 指標,及時掌握部署造成的 session 中斷。

良好的錯誤體驗本身即為功能,請認真對待。

Error Handling

Nuxt is full-stack, so errors can happen on four fronts: Vue rendering, app startup, Nitro server code, and JS chunk downloads. Each has a distinct toolkit.

Vue errors

Inside components, onErrorCaptured works as in any Vue app. Globally, register once via a plugin:

// app/plugins/error-report.ts
export default defineNuxtPlugin((nuxtApp) => {
  nuxtApp.vueApp.config.errorHandler = (error, instance, info) => {
    // ship to Sentry / Datadog / your backend
  }

  nuxtApp.hook('vue:error', (error, instance, info) => {
    // runs even for handled errors
  })
})

vue:error is Nuxt's version of onErrorCaptured — it receives every error that propagates up the Vue tree.

Startup errors

Errors thrown during plugin init, SSR, or client mount fire the app:error hook. Listen to it to reset analytics, log, or purge caches.

Nitro server errors

Server-side errors can't be caught by a Vue boundary. They render the error page instead (see below). For custom behavior, throw createError({...}) inside your event handler:

// server/api/posts/[id].get.ts
export default defineEventHandler((event) => {
  const post = findPost(event.context.params!.id)
  if (!post) {
    throw createError({ status: 404, statusText: 'Post not found' })
  }
  return post
})

Chunk loading errors

When a user sits on a page and you redeploy, their cached chunk URLs can 404 on the next navigation. Nuxt ships a built-in hard-reload fallback. To customize, set experimental.emitRouteChunkError to 'manual' and handle the nuxt:chunk-error event yourself.

error.vue — the fallback page

When Nuxt hits a fatal error (uncaught server error, or createError({ fatal: true }) on the client), it renders ~/error.vue:

<!-- error.vue -->
<script setup lang="ts">
import type { NuxtError } from '#app'

const props = defineProps<{ error: NuxtError }>()

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

<template>
  <section>
    <h1>{{ error.statusCode }}</h1>
    <p>{{ error.statusMessage }}</p>
    <button @click="handleError">Go home</button>
  </section>
</template>

Things to remember:

  • error.vue is a separate page render, so route middleware runs again. Use useError() to detect when you're in error mode.
  • JSON clients (those sending Accept: application/json) get a JSON body instead of the error page.

Creating errors: createError

import { createError } from '#app'

if (!data.value) {
  throw createError({
    status: 404,
    statusText: 'Page Not Found',
  })
}
  • On the server (setup, plugins, handlers), throwing a createError triggers the fatal error page.
  • On the client, it's a non-fatal error by default; pass { fatal: true } to escalate to the error page.

Keep statusText short and ASCII-only. For long descriptions or i18n, use message or a payload data.

showError and clearError

  • showError(err) → imperatively trigger the error page.
  • clearError({ redirect: '/' }) → dismiss the current error and optionally navigate.

Prefer throw createError(...) in setup / handlers; use showError when you already caught the error and want to surface it manually.

NuxtErrorBoundary — localized error UI

Don't blow up the whole page for a broken widget. Wrap it:

<template>
  <NuxtErrorBoundary @error="report">
    <RemoteChart />
    <template #error="{ error, clearError }">
      <p>Chart failed to load.</p>
      <button @click="clearError">Retry</button>
    </template>
  </NuxtErrorBoundary>
</template>

<script setup lang="ts">
function report (err: unknown) { /* log */ }
</script>

Setting error = null re-renders the default slot — so make sure you resolved the cause first, otherwise the slot re-throws.

useError() — read the active error

Inside middleware, plugins, or layouts you can check:

const error = useError()
if (error.value) {
  // running inside the error page flow
}

Handy for skipping auth checks or hiding chrome while the error page is active.

Checklist

  • Register a global handler in a plugin (errorHandler + vue:error).
  • Provide error.vue with a friendly retry path.
  • Throw createError from Nitro handlers for 4xx/5xx responses.
  • Wrap risky widgets in NuxtErrorBoundary.
  • Ship chunk-reload telemetry so you know when deploys break sessions.

Good error UX is a feature — treat it like one.