Next.js 错误处理 🚨
🎯 概述
Next.js 提供了完善的错误处理机制,包括错误边界、自定义错误页面、全局错误处理等功能,帮助开发者优雅地处理应用中的各种错误情况。
📦 错误类型
1. 客户端错误
- 组件渲染错误
- 事件处理错误
- 异步操作错误
2. 服务端错误
- 数据获取错误
- API 路由错误
- 服务器渲染错误
3. 路由错误
- 404 页面未找到
- 500 服务器错误
🚀 error.tsx 文件
基本用法
tsx
// app/error.tsx
'use client'
export default function Error({
error,
reset,
}: {
error: Error & { digest?: string }
reset: () => void
}) {
return (
<div>
<h2>出错了!</h2>
<p>{error.message}</p>
<button onClick={() => reset()}>重试</button>
</div>
)
}嵌套错误处理
tsx
// app/dashboard/error.tsx
'use client'
export default function DashboardError({
error,
reset,
}: {
error: Error
reset: () => void
}) {
return (
<div className="error-container">
<h2>仪表板加载失败</h2>
<p>{error.message}</p>
<button onClick={reset}>重新加载</button>
</div>
)
}📝 全局错误处理
global-error.tsx
tsx
// app/global-error.tsx
'use client'
export default function GlobalError({
error,
reset,
}: {
error: Error & { digest?: string }
reset: () => void
}) {
return (
<html>
<body>
<h2>应用出现严重错误</h2>
<p>{error.message}</p>
<button onClick={() => reset()}>重启应用</button>
</body>
</html>
)
}🎨 自定义 404 页面
not-found.tsx
tsx
// app/not-found.tsx
import Link from 'next/link'
export default function NotFound() {
return (
<div className="not-found">
<h1>404</h1>
<h2>页面未找到</h2>
<p>抱歉,您访问的页面不存在。</p>
<Link href="/">返回首页</Link>
</div>
)
}触发 404
tsx
// app/posts/[id]/page.tsx
import { notFound } from 'next/navigation'
async function getPost(id: string) {
const res = await fetch(`https://api.example.com/posts/${id}`)
if (!res.ok) return null
return res.json()
}
export default async function Post({ params }: { params: { id: string } }) {
const post = await getPost(params.id)
if (!post) {
notFound()
}
return <article>{post.title}</article>
}🔄 错误边界
React Error Boundary
tsx
// components/ErrorBoundary.tsx
'use client'
import React from 'react'
interface Props {
children: React.ReactNode
fallback?: React.ReactNode
}
interface State {
hasError: boolean
error?: Error
}
export class ErrorBoundary extends React.Component<Props, State> {
constructor(props: Props) {
super(props)
this.state = { hasError: false }
}
static getDerivedStateFromError(error: Error): State {
return { hasError: true, error }
}
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
console.error('Error caught by boundary:', error, errorInfo)
}
render() {
if (this.state.hasError) {
return this.props.fallback || <div>出错了</div>
}
return this.props.children
}
}使用:
tsx
<ErrorBoundary fallback={<div>加载失败</div>}>
<MyComponent />
</ErrorBoundary>📊 API 路由错误处理
基本错误处理
tsx
// app/api/users/route.ts
import { NextResponse } from 'next/server'
export async function GET() {
try {
const users = await fetchUsers()
return NextResponse.json(users)
} catch (error) {
console.error('获取用户失败:', error)
return NextResponse.json(
{ error: '获取用户失败' },
{ status: 500 }
)
}
}自定义错误类
tsx
// lib/errors.ts
export class APIError extends Error {
constructor(
message: string,
public statusCode: number = 500,
public code?: string
) {
super(message)
this.name = 'APIError'
}
}
// app/api/posts/route.ts
import { APIError } from '@/lib/errors'
import { NextResponse } from 'next/server'
export async function POST(request: Request) {
try {
const body = await request.json()
if (!body.title) {
throw new APIError('标题不能为空', 400, 'MISSING_TITLE')
}
const post = await createPost(body)
return NextResponse.json(post)
} catch (error) {
if (error instanceof APIError) {
return NextResponse.json(
{ error: error.message, code: error.code },
{ status: error.statusCode }
)
}
return NextResponse.json(
{ error: '服务器错误' },
{ status: 500 }
)
}
}🎯 数据获取错误处理
Server Component 错误处理
tsx
// app/posts/page.tsx
async function getPosts() {
const res = await fetch('https://api.example.com/posts', {
next: { revalidate: 60 }
})
if (!res.ok) {
throw new Error('获取文章失败')
}
return res.json()
}
export default async function PostsPage() {
try {
const posts = await getPosts()
return (
<div>
{posts.map(post => (
<article key={post.id}>{post.title}</article>
))}
</div>
)
} catch (error) {
return <div>加载文章失败</div>
}
}使用 Suspense 和 Error Boundary
tsx
// app/posts/page.tsx
import { Suspense } from 'react'
import { ErrorBoundary } from '@/components/ErrorBoundary'
async function Posts() {
const posts = await getPosts()
return (
<div>
{posts.map(post => (
<article key={post.id}>{post.title}</article>
))}
</div>
)
}
export default function PostsPage() {
return (
<ErrorBoundary fallback={<div>加载失败</div>}>
<Suspense fallback={<div>加载中...</div>}>
<Posts />
</Suspense>
</ErrorBoundary>
)
}🔍 错误日志和监控
错误日志记录
tsx
// lib/logger.ts
export function logError(error: Error, context?: any) {
console.error('Error:', {
message: error.message,
stack: error.stack,
context,
timestamp: new Date().toISOString(),
})
// 发送到错误监控服务
if (process.env.NODE_ENV === 'production') {
// 例如:Sentry, LogRocket 等
}
}使用:
tsx
// app/error.tsx
'use client'
import { useEffect } from 'react'
import { logError } from '@/lib/logger'
export default function Error({ error, reset }) {
useEffect(() => {
logError(error, { page: 'home' })
}, [error])
return (
<div>
<h2>出错了</h2>
<button onClick={reset}>重试</button>
</div>
)
}📚 最佳实践
1. 分层错误处理
tsx
// 全局错误
// app/global-error.tsx
// 布局错误
// app/layout-error.tsx
// 页面错误
// app/page-error.tsx
// 组件错误
// components/ErrorBoundary.tsx2. 友好的错误提示
tsx
'use client'
export default function Error({ error, reset }) {
const getErrorMessage = (error: Error) => {
if (error.message.includes('network')) {
return '网络连接失败,请检查您的网络'
}
if (error.message.includes('timeout')) {
return '请求超时,请稍后重试'
}
return '出现了一些问题,请稍后重试'
}
return (
<div>
<h2>{getErrorMessage(error)}</h2>
<button onClick={reset}>重试</button>
</div>
)
}3. 错误恢复策略
tsx
'use client'
import { useState } from 'react'
export default function Error({ error, reset }) {
const [retryCount, setRetryCount] = useState(0)
const handleRetry = () => {
setRetryCount(prev => prev + 1)
reset()
}
if (retryCount >= 3) {
return (
<div>
<h2>多次重试失败</h2>
<p>请联系技术支持</p>
</div>
)
}
return (
<div>
<h2>出错了</h2>
<button onClick={handleRetry}>
重试 ({retryCount}/3)
</button>
</div>
)
}🔗 相关资源
下一步:学习 Next.js 服务端渲染 (SSR)。