Next.js错误处理 #
一、错误处理概述 #
1.1 错误类型 #
| 错误类型 | 处理文件 | 说明 |
|---|---|---|
| 运行时错误 | error.tsx | 组件渲染错误 |
| 全局错误 | global-error.tsx | 根布局错误 |
| 404错误 | not-found.tsx | 页面未找到 |
| 服务端错误 | try/catch | API错误 |
1.2 错误边界 #
Next.js 使用 React 错误边界捕获错误,错误边界必须是一个客户端组件。
二、error.tsx #
2.1 基本用法 #
tsx
'use client'
export default function Error({
error,
reset,
}: {
error: Error & { digest?: string }
reset: () => void
}) {
return (
<div>
<h2>出错了!</h2>
<button onClick={() => reset()}>重试</button>
</div>
)
}
2.2 错误属性 #
| 属性 | 类型 | 说明 |
|---|---|---|
| error | Error | 错误对象 |
| error.digest | string | 错误摘要(服务端错误) |
| reset | () => void | 重试函数 |
2.3 完整错误页面 #
tsx
'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 className="min-h-screen flex items-center justify-center bg-gray-50">
<div className="max-w-md w-full text-center">
<div className="text-6xl mb-4">😢</div>
<h2 className="text-2xl font-bold mb-4">出错了!</h2>
<p className="text-gray-600 mb-6">
{error.message || '发生了意外错误'}
</p>
<div className="flex gap-4 justify-center">
<button
onClick={() => reset()}
className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
>
重试
</button>
<a
href="/"
className="px-4 py-2 bg-gray-200 text-gray-700 rounded hover:bg-gray-300"
>
返回首页
</a>
</div>
</div>
</div>
)
}
2.4 错误日志 #
tsx
'use client'
import { useEffect } from 'react'
export default function Error({
error,
reset,
}: {
error: Error & { digest?: string }
reset: () => void
}) {
useEffect(() => {
fetch('/api/log-error', {
method: 'POST',
body: JSON.stringify({
message: error.message,
stack: error.stack,
digest: error.digest,
url: window.location.href,
timestamp: new Date().toISOString(),
}),
})
}, [error])
return (
<div>
<h2>出错了</h2>
<button onClick={reset}>重试</button>
</div>
)
}
三、global-error.tsx #
3.1 概念 #
global-error.tsx 用于处理根布局中的错误,必须包含 html 和 body 标签。
3.2 基本用法 #
tsx
'use client'
export default function GlobalError({
error,
reset,
}: {
error: Error & { digest?: string }
reset: () => void
}) {
return (
<html lang="zh-CN">
<body>
<div className="min-h-screen flex items-center justify-center">
<div className="text-center">
<h2>严重错误</h2>
<button onClick={() => reset()}>重试</button>
</div>
</div>
</body>
</html>
)
}
3.3 完整示例 #
tsx
'use client'
import { useEffect } from 'react'
export default function GlobalError({
error,
reset,
}: {
error: Error & { digest?: string }
reset: () => void
}) {
useEffect(() => {
console.error('全局错误:', error)
}, [error])
return (
<html lang="zh-CN">
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>错误</title>
</head>
<body className="bg-gray-100">
<div className="min-h-screen flex items-center justify-center">
<div className="max-w-md w-full bg-white rounded-lg shadow-lg p-8 text-center">
<div className="text-6xl mb-4">⚠️</div>
<h1 className="text-2xl font-bold text-red-600 mb-4">
系统错误
</h1>
<p className="text-gray-600 mb-6">
应用遇到了严重错误,请刷新页面重试
</p>
<button
onClick={() => reset()}
className="px-6 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
>
刷新页面
</button>
</div>
</div>
</body>
</html>
)
}
四、not-found.tsx #
4.1 基本用法 #
tsx
import Link from 'next/link'
export default function NotFound() {
return (
<div className="min-h-screen flex items-center justify-center">
<div className="text-center">
<h1 className="text-6xl font-bold text-gray-300">404</h1>
<h2 className="text-2xl font-medium mt-4">页面未找到</h2>
<p className="text-gray-500 mt-2">
您访问的页面不存在
</p>
<Link
href="/"
className="mt-6 inline-block px-4 py-2 bg-blue-500 text-white rounded"
>
返回首页
</Link>
</div>
</div>
)
}
4.2 触发404 #
使用notFound函数
tsx
import { notFound } from 'next/navigation'
interface PageProps {
params: Promise<{ id: string }>
}
export default async function ProductPage({ params }: PageProps) {
const { id } = await params
const product = await getProduct(id)
if (!product) {
notFound()
}
return <div>{product.name}</div>
}
4.3 嵌套404页面 #
text
app/
├── not-found.tsx # 全局404
├── blog/
│ ├── not-found.tsx # 博客404
│ └── [slug]/
│ └── page.tsx
博客404页面
tsx
import Link from 'next/link'
export default function BlogNotFound() {
return (
<div className="text-center py-16">
<h2 className="text-2xl font-bold mb-4">文章未找到</h2>
<p className="text-gray-500 mb-6">
您访问的文章不存在或已被删除
</p>
<Link href="/blog" className="text-blue-500 hover:underline">
查看所有文章
</Link>
</div>
)
}
五、加载状态 #
5.1 loading.tsx #
tsx
export default function Loading() {
return (
<div className="min-h-screen flex items-center justify-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-500"></div>
</div>
)
}
5.2 骨架屏 #
tsx
export default function Loading() {
return (
<div className="animate-pulse">
<div className="h-8 bg-gray-200 rounded w-1/2 mb-4"></div>
<div className="h-4 bg-gray-200 rounded w-3/4 mb-2"></div>
<div className="h-4 bg-gray-200 rounded w-1/2 mb-4"></div>
<div className="h-64 bg-gray-200 rounded mb-4"></div>
<div className="h-4 bg-gray-200 rounded w-full mb-2"></div>
<div className="h-4 bg-gray-200 rounded w-5/6"></div>
</div>
)
}
5.3 目录结构 #
text
app/
├── loading.tsx # 全局加载
├── blog/
│ ├── loading.tsx # 博客加载
│ └── page.tsx
六、服务端错误处理 #
6.1 try/catch #
tsx
interface PageProps {
params: Promise<{ id: string }>
}
export default async function Page({ params }: PageProps) {
try {
const { id } = await params
const data = await getData(id)
return <div>{data.title}</div>
} catch (error) {
console.error('获取数据失败:', error)
throw new Error('获取数据失败')
}
}
6.2 错误边界嵌套 #
tsx
import { ErrorBoundary } from 'react-error-boundary'
function ErrorFallback({ error, resetErrorBoundary }) {
return (
<div role="alert">
<p>出错了:</p>
<pre>{error.message}</pre>
<button onClick={resetErrorBoundary}>重试</button>
</div>
)
}
export default function Page() {
return (
<ErrorBoundary FallbackComponent={ErrorFallback}>
<MyComponent />
</ErrorBoundary>
)
}
七、错误处理最佳实践 #
7.1 错误分类 #
tsx
'use client'
import { useEffect } from 'react'
export default function Error({
error,
reset,
}: {
error: Error & { digest?: string }
reset: () => void
}) {
useEffect(() => {
if (error.digest) {
console.error('服务端错误:', error.digest)
} else {
console.error('客户端错误:', error.message)
}
}, [error])
return (
<div>
<h2>出错了</h2>
<button onClick={reset}>重试</button>
</div>
)
}
7.2 错误恢复 #
tsx
'use client'
import { useState } from 'react'
export default function Error({
error,
reset,
}: {
error: Error & { digest?: string }
reset: () => void
}) {
const [retryCount, setRetryCount] = useState(0)
const handleRetry = () => {
setRetryCount(prev => prev + 1)
reset()
}
return (
<div>
<h2>出错了</h2>
<p>重试次数: {retryCount}</p>
<button onClick={handleRetry}>重试</button>
</div>
)
}
7.3 错误上报 #
tsx
'use client'
import { useEffect } from 'react'
function reportError(error: Error, context: Record<string, any>) {
if (typeof window !== 'undefined' && window.Sentry) {
window.Sentry.captureException(error, { extra: context })
}
}
export default function Error({
error,
reset,
}: {
error: Error & { digest?: string }
reset: () => void
}) {
useEffect(() => {
reportError(error, {
url: window.location.href,
userAgent: navigator.userAgent,
timestamp: new Date().toISOString(),
})
}, [error])
return (
<div>
<h2>出错了</h2>
<button onClick={reset}>重试</button>
</div>
)
}
八、综合示例 #
8.1 完整错误处理 #
text
app/
├── error.tsx # 全局错误
├── global-error.tsx # 根布局错误
├── not-found.tsx # 404页面
├── loading.tsx # 加载状态
└── dashboard/
├── error.tsx # 仪表盘错误
├── not-found.tsx # 仪表盘404
└── loading.tsx # 仪表盘加载
全局错误
tsx
'use client'
import { useEffect } from 'react'
import Link from 'next/link'
export default function Error({
error,
reset,
}: {
error: Error & { digest?: string }
reset: () => void
}) {
useEffect(() => {
console.error('错误:', error)
}, [error])
return (
<div className="min-h-screen bg-gray-50 flex items-center justify-center p-4">
<div className="max-w-lg w-full bg-white rounded-lg shadow-lg p-8">
<div className="text-center">
<div className="text-5xl mb-4">😵</div>
<h1 className="text-2xl font-bold text-gray-900 mb-2">
出错了
</h1>
<p className="text-gray-600 mb-6">
{error.message || '发生了意外错误,请稍后重试'}
</p>
</div>
<div className="flex flex-col sm:flex-row gap-3 justify-center">
<button
onClick={reset}
className="px-6 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition"
>
重试
</button>
<Link
href="/"
className="px-6 py-2 bg-gray-100 text-gray-700 rounded-lg hover:bg-gray-200 transition text-center"
>
返回首页
</Link>
</div>
{process.env.NODE_ENV === 'development' && (
<details className="mt-6">
<summary className="text-sm text-gray-500 cursor-pointer">
错误详情
</summary>
<pre className="mt-2 p-4 bg-gray-100 rounded text-xs overflow-auto">
{error.stack}
</pre>
</details>
)}
</div>
</div>
)
}
九、总结 #
错误处理要点:
| 文件 | 用途 | 特点 |
|---|---|---|
| error.tsx | 运行时错误 | 客户端组件 |
| global-error.tsx | 根布局错误 | 包含html/body |
| not-found.tsx | 404页面 | 可嵌套 |
| loading.tsx | 加载状态 | 自动显示 |
下一步,让我们学习数据获取!
最后更新:2026-03-28