You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

NextJS静态渲染时如何捕获客户端致命错误并上报日志

捕获Next.js静态渲染应用的客户端致命错误并上报

一、利用React错误边界(Error Boundary)

Next.js的Pages Router和App Router都支持通过错误边界捕获组件树内的渲染错误,这是拦截客户端组件级致命错误的核心方案:

Pages Router 实现

在_app.tsx中封装自定义错误边界组件:

import type { AppProps } from 'next/app'
import React from 'react'

class ErrorBoundary extends React.Component<{ children: React.ReactNode }, { hasError: boolean }> {
  constructor(props) {
    super(props)
    this.state = { hasError: false }
  }

  static getDerivedStateFromError(error: Error) {
    return { hasError: true }
  }

  componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
    this.reportError(error, errorInfo)
  }

  reportError(error: Error, errorInfo: React.ErrorInfo) {
    // 自定义上报逻辑,替换为你的日志接口
    fetch('/api/report-error', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        message: error.message,
        stack: error.stack,
        componentStack: errorInfo.componentStack,
        pageUrl: window.location.href,
        timestamp: new Date().toISOString(),
      }),
    })
  }

  render() {
    if (this.state.hasError) {
      return <div className="error-fallback">页面加载出错,请稍后重试</div>
    }
    return this.props.children
  }
}

function MyApp({ Component, pageProps }: AppProps) {
  return (
    <ErrorBoundary>
      <Component {...pageProps} />
    </ErrorBoundary>
  )
}

export default MyApp

App Router 实现(Next.js 13+)

在app/error.tsx中定义全局错误边界(这不是404页面,专门处理客户端渲染错误):

'use client'

import { useEffect } from 'react'

export default function Error({ error, reset }: { error: Error & { digest?: string }, reset: () => void }) {
  useEffect(() => {
    fetch('/api/report-error', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        message: error.message,
        stack: error.stack,
        pageUrl: window.location.href,
        timestamp: new Date().toISOString(),
      }),
    })
  }, [error])

  return (
    <div>
      <h2>页面加载出错了</h2>
      <button onClick={() => reset()}>点击重试</button>
    </div>
  )
}

二、全局监听未捕获的JS错误

除了组件树内的错误,还需要监听全局层面的未捕获同步错误和Promise拒绝:
在客户端入口文件(Pages Router的_app.tsx或App Router的layout.tsx)中添加监听:

'use client'

import { useEffect } from 'react'

function GlobalErrorListener() {
  useEffect(() => {
    // 监听未捕获同步错误
    const handleUncaughtError = (event: ErrorEvent) => {
      event.preventDefault()
      fetch('/api/report-error', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          message: event.error?.message || '未知全局错误',
          stack: event.error?.stack,
          type: 'uncaughtError',
          pageUrl: window.location.href,
          timestamp: new Date().toISOString(),
        }),
      })
    }

    // 监听未处理的Promise拒绝
    const handleRejection = (event: PromiseRejectionEvent) => {
      event.preventDefault()
      fetch('/api/report-error', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          message: event.reason?.message || 'Promise未处理拒绝',
          stack: event.reason?.stack,
          type: 'unhandledRejection',
          pageUrl: window.location.href,
          timestamp: new Date().toISOString(),
        }),
      })
    }

    window.addEventListener('error', handleUncaughtError)
    window.addEventListener('unhandledrejection', handleRejection)

    return () => {
      window.removeEventListener('error', handleUncaughtError)
      window.removeEventListener('unhandledrejection', handleRejection)
    }
  }, [])

  return null
}

// 在Pages Router的_app.tsx中引入
function MyApp({ Component, pageProps }: AppProps) {
  return (
    <>
      <GlobalErrorListener />
      <ErrorBoundary>
        <Component {...pageProps} />
      </ErrorBoundary>
    </>
  )
}

// 在App Router的layout.tsx中引入
export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="zh-CN">
      <body>
        <GlobalErrorListener />
        {children}
      </body>
    </html>
  )
}

三、处理静态渲染的Hydrate错误

静态生成(SSG)页面在客户端hydrate时可能出现内容不匹配错误,可通过next.config.js开启调试日志,同时上述错误边界会捕获这类错误:

// next.config.js
module.exports = {
  reactStrictMode: true,
  logging: {
    fetches: { fullUrl: true },
  },
}

四、优化建议

  • 给上报接口添加防抖逻辑,避免同一错误重复上报
  • 上报时可附加用户标识、浏览器UA信息,提升调试效率
  • 静态渲染页面的客户端错误多源于服务端/客户端内容不一致,重点检查动态数据、组件状态初始化逻辑

内容的提问来源于stack exchange,提问作者Kevin Danikowski

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.30 23:27:45