錯誤處理

error.js 檔案約定讓您能優雅地處理嵌套路由中的意外運行時錯誤。

  • 自動將路由區段及其嵌套子項目包裹在 React 錯誤邊界
  • 利用檔案系統層級結構來調整粒度,為特定區段建立專屬的錯誤 UI
  • 將錯誤隔離在受影響的區段,同時保持應用程式其餘部分正常運作
  • 添加功能以嘗試從錯誤中恢復,無需完整頁面重新載入

透過在路由區段內添加 error.js 檔案並導出 React 組件來建立錯誤 UI:

error.js 特殊檔案
'use client' // 錯誤組件必須是客戶端組件

import { useEffect } from 'react'

export default function Error({
  error,
  reset,
}: {
  error: Error & { digest?: string }
  reset: () => void
}) {
  useEffect(() => {
    // 將錯誤記錄到錯誤報告服務
    console.error(error)
  }, [error])

  return (
    <div>
      <h2>發生錯誤!</h2>
      <button
        onClick={
          // 嘗試透過重新渲染區段來恢復
          () => reset()
        }
      >
        重試
      </button>
    </div>
  )
}
'use client' // 錯誤組件必須是客戶端組件

import { useEffect } from 'react'

export default function Error({ error, reset }) {
  useEffect(() => {
    // 將錯誤記錄到錯誤報告服務
    console.error(error)
  }, [error])

  return (
    <div>
      <h2>發生錯誤!</h2>
      <button
        onClick={
          // 嘗試透過重新渲染區段來恢復
          () => reset()
        }
      >
        重試
      </button>
    </div>
  )
}

error.js 運作原理

error.js 運作方式
  • error.js 會自動建立一個 React 錯誤邊界包裹嵌套的子區段或 page.js 組件
  • error.js 檔案導出的 React 組件將作為後備組件使用
  • 如果在錯誤邊界內拋出錯誤,錯誤會被封裝,並渲染後備組件
  • 當後備錯誤組件處於活動狀態時,錯誤邊界上方的布局會保持其狀態並維持可互動性,且錯誤組件可以顯示從錯誤中恢復的功能

從錯誤中恢復

有時錯誤的原因可能是暫時性的。在這些情況下,只需重試可能就能解決問題。

錯誤組件可以使用 reset() 函數提示用戶嘗試從錯誤中恢復。執行時,該函數會嘗試重新渲染錯誤邊界的內容。如果成功,後備錯誤組件將被重新渲染的結果取代。

'use client'

export default function Error({
  error,
  reset,
}: {
  error: Error & { digest?: string }
  reset: () => void
}) {
  return (
    <div>
      <h2>發生錯誤!</h2>
      <button onClick={() => reset()}>重試</button>
    </div>
  )
}
'use client'

export default function Error({ error, reset }) {
  return (
    <div>
      <h2>發生錯誤!</h2>
      <button onClick={() => reset()}>重試</button>
    </div>
  )
}

嵌套路由

透過特殊檔案建立的 React 組件會在特定的嵌套層級結構中渲染。

例如,包含 layout.jserror.js 檔案的兩個嵌套區段會以下列簡化的組件層級結構渲染:

嵌套錯誤組件層級結構

嵌套組件層級結構會影響嵌套路由中 error.js 檔案的行為:

  • 錯誤會冒泡到最近的父級錯誤邊界。這意味著 error.js 檔案將處理其所有嵌套子區段的錯誤。透過在路由的嵌套資料夾不同層級放置 error.js 檔案,可以實現更細緻或更粗略的錯誤 UI。
  • error.js 邊界不會處理在同一區段的 layout.js 組件中拋出的錯誤,因為錯誤邊界嵌套在該布局組件內部。

處理布局中的錯誤

error.js 邊界不會捕獲同一區段的 layout.jstemplate.js 組件中拋出的錯誤。這種刻意設計的層級結構能在錯誤發生時,保持兄弟路由之間共享的重要 UI(如導航)可見且可運作。

要處理特定布局或模板內的錯誤,請在布局的父級區段放置 error.js 檔案。

要處理根布局或模板中的錯誤,請使用 error.js 的變體 global-error.js

處理根布局中的錯誤

根目錄的 app/error.js 邊界不會捕獲根 app/layout.jsapp/template.js 組件中拋出的錯誤。

要專門處理這些根組件中的錯誤,請在根 app 目錄使用 error.js 的變體 app/global-error.js

與根 error.js 不同,global-error.js 錯誤邊界會包裹整個應用程式,其後備組件在活動時會取代根布局。因此請注意,global-error.js 必須定義自己的 <html><body> 標籤。

global-error.js 是最不精細的錯誤 UI,可視為整個應用程式的「全局捕獲」錯誤處理。由於根組件通常較少動態變化,且其他 error.js 邊界會捕獲大多數錯誤,因此它不太常被觸發。

即使定義了 global-error.js,仍建議定義根 error.js,其後備組件將在根布局內部渲染,包含全局共享的 UI 和品牌標識。

'use client'

export default function GlobalError({
  error,
  reset,
}: {
  error: Error & { digest?: string }
  reset: () => void
}) {
  return (
    <html>
      <body>
        <h2>發生錯誤!</h2>
        <button onClick={() => reset()}>重試</button>
      </body>
    </html>
  )
}
'use client'

export default function GlobalError({ error, reset }) {
  return (
    <html>
      <body>
        <h2>發生錯誤!</h2>
        <button onClick={() => reset()}>重試</button>
      </body>
    </html>
  )
}

處理伺服器錯誤

如果在伺服器組件中拋出錯誤,Next.js 會將 Error 物件(在生產環境中會移除敏感錯誤資訊)作為 error 屬性傳遞給最近的 error.js 檔案。

保護敏感錯誤資訊

在生產環境中,傳遞給客戶端的 Error 物件僅包含通用的 messagedigest 屬性。

這是一種安全預防措施,避免將錯誤中包含的潛在敏感細節洩露給客戶端。

message 屬性包含關於錯誤的通用訊息,digest 屬性包含自動生成的錯誤哈希值,可用於匹配伺服器端日誌中的相應錯誤。

在開發環境中,傳遞給客戶端的 Error 物件會被序列化,並包含原始錯誤的 message 以便於除錯。