error.js

error 檔案讓您能處理意外的執行時錯誤並顯示後備 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 將路由區段及其嵌套子元件包裹在 React 錯誤邊界中。當邊界內發生錯誤時,error 元件會顯示為後備 UI。

error.js 運作方式

須知事項:

  • React DevTools 可讓您切換錯誤邊界來測試錯誤狀態
  • 若希望錯誤冒泡到父級錯誤邊界,可在渲染 error 元件時使用 throw

參考

屬性

error

傳遞給 error.js 客戶端元件的 Error 物件實例。

須知事項: 在開發環境中,傳遞給客戶端的 Error 物件會被序列化並包含原始錯誤的 message 以便除錯。但在生產環境中行為不同,以避免將錯誤中可能包含的敏感細節洩漏給客戶端。

error.message

  • 從客戶端元件傳遞的錯誤會顯示原始 Error 訊息
  • 從伺服器元件傳遞的錯誤會顯示帶有識別碼的通用訊息。這是為了防止洩漏敏感細節。您可以使用 errors.digest 下的識別碼來匹配對應的伺服器端日誌

error.digest

自動生成的錯誤拋出雜湊值。可用於匹配伺服器端日誌中的對應錯誤。

reset

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

錯誤元件可以使用 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>
  )
}

範例

全域錯誤

雖然較不常見,但您可以使用位於根應用目錄中的 global-error.js 來處理根佈局或模板中的錯誤,即使在啟用國際化時也是如此。全域錯誤 UI 必須定義自己的 <html><body> 標籤。此檔案在啟用時會取代根佈局或模板。

'use client' // 錯誤邊界必須是客戶端元件

export default function GlobalError({
  error,
  reset,
}: {
  error: Error & { digest?: string }
  reset: () => void
}) {
  return (
    // global-error 必須包含 html 和 body 標籤
    <html>
      <body>
        <h2>發生錯誤!</h2>
        <button onClick={() => reset()}>重試</button>
      </body>
    </html>
  )
}
'use client' // 錯誤邊界必須是客戶端元件

export default function GlobalError({ error, reset }) {
  return (
    // global-error 必須包含 html 和 body 標籤
    <html>
      <body>
        <h2>發生錯誤!</h2>
        <button onClick={() => reset()}>重試</button>
      </body>
    </html>
  )
}

使用自訂錯誤邊界優雅恢復

當客戶端渲染失敗時,顯示最後已知的伺服器渲染 UI 可以提供更好的使用者體驗。

GracefullyDegradingErrorBoundary 是一個自訂錯誤邊界範例,它會捕捉並保留錯誤發生前的當前 HTML。如果發生渲染錯誤,它會重新渲染捕捉到的 HTML 並顯示持續通知欄來告知使用者。

'use client'

import React, { Component, ErrorInfo, ReactNode } from 'react'

interface ErrorBoundaryProps {
  children: ReactNode
  onError?: (error: Error, errorInfo: ErrorInfo) => void
}

interface ErrorBoundaryState {
  hasError: boolean
}

export class GracefullyDegradingErrorBoundary extends Component<
  ErrorBoundaryProps,
  ErrorBoundaryState
> {
  private contentRef: React.RefObject<HTMLDivElement>

  constructor(props: ErrorBoundaryProps) {
    super(props)
    this.state = { hasError: false }
    this.contentRef = React.createRef()
  }

  static getDerivedStateFromError(_: Error): ErrorBoundaryState {
    return { hasError: true }
  }

  componentDidCatch(error: Error, errorInfo: ErrorInfo) {
    if (this.props.onError) {
      this.props.onError(error, errorInfo)
    }
  }

  render() {
    if (this.state.hasError) {
      // 渲染當前 HTML 內容而不進行水合
      return (
        <>
          <div
            ref={this.contentRef}
            suppressHydrationWarning
            dangerouslySetInnerHTML={{
              __html: this.contentRef.current?.innerHTML || '',
            }}
          />
          <div className="fixed bottom-0 left-0 right-0 bg-red-600 text-white py-4 px-6 text-center">
            <p className="font-semibold">
              頁面渲染期間發生錯誤
            </p>
          </div>
        </>
      )
    }

    return <div ref={this.contentRef}>{this.props.children}</div>
  }
}

export default GracefullyDegradingErrorBoundary
'use client'

import React, { Component, createRef } from 'react'

class GracefullyDegradingErrorBoundary extends Component {
  constructor(props) {
    super(props)
    this.state = { hasError: false }
    this.contentRef = createRef()
  }

  static getDerivedStateFromError(_) {
    return { hasError: true }
  }

  componentDidCatch(error, errorInfo) {
    if (this.props.onError) {
      this.props.onError(error, errorInfo)
    }
  }

  render() {
    if (this.state.hasError) {
      // 渲染當前 HTML 內容而不進行水合
      return (
        <>
          <div
            ref={this.contentRef}
            suppressHydrationWarning
            dangerouslySetInnerHTML={{
              __html: this.contentRef.current?.innerHTML || '',
            }}
          />
          <div className="fixed bottom-0 left-0 right-0 bg-red-600 text-white py-4 px-6 text-center">
            <p className="font-semibold">
              頁面渲染期間發生錯誤
            </p>
          </div>
        </>
      )
    }

    return <div ref={this.contentRef}>{this.props.children}</div>
  }
}

export default GracefullyDegradingErrorBoundary

版本歷史

版本變更內容
v15.2.0在開發環境中也顯示 global-error
v13.1.0引入 global-error
v13.0.0引入 error