前面我们学习了很多功能性的内容,本节来聊聊错误处理。错误处理是构建健壮应用的关键,好的错误处理不仅能提升用户体验,还能让调试更容易。
错误处理概述
Next.js App Router 提供了多种错误处理方式:
- error.tsx - 运行时错误
- not-found.tsx - 404 错误
- global-error.tsx - 全局错误
- loading.tsx - 加载状态(虽然不是错误,但与错误处理相关)
理解每种方式的适用场景很重要。
error.tsx - 错误边界
error.tsx 文件用于捕获运行时错误,这是最常用的错误处理方式。
基本 error.tsx
// app/dashboard/error.tsx
'use client' // 必须是客户端组件
export default function Error({
error,
reset,
}: {
error: Error & { digest?: string }
reset: () => void
}) {
return (
<div className="error-container">
<h2>出错了!</h2>
<p>{error.message}</p>
<button onClick={reset}>重试</button>
</div>
)
}
error.tsx 特性
error.tsx 有几个重要特性需要注意:
- 必须是客户端组件('use client')
- 只捕获运行时错误,不捕获构建时错误
- 替换出错的子树
- 提供重置功能
自定义错误页面
一个更友好的错误页面:
// app/error.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">
<div className="text-center">
<h1 className="text-6xl font-bold text-gray-200">500</h1>
<h2 className="text-2xl font-semibold mb-4">服务器错误</h2>
<p className="text-gray-600 mb-6">
抱歉,处理您的请求时出现了问题
</p>
<div className="space-x-4">
<button
onClick={reset}
className="px-6 py-2 bg-blue-500 text-white rounded"
>
重试
</button>
<a
href="/"
className="px-6 py-2 border border-gray-300 rounded"
>
返回首页
</a>
</div>
{process.env.NODE_ENV === 'development' && (
<details className="mt-6 text-left">
<summary className="cursor-pointer">错误详情</summary>
<pre className="mt-2 p-4 bg-gray-100 rounded overflow-auto">
{error.stack}
</pre>
</details>
)}
</div>
</div>
)
}
not-found.tsx - 404 错误
当用户访问不存在的页面时,显示 not-found.tsx。
基本 not-found.tsx
// app/not-found.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-9xl font-bold text-gray-200">404</h1>
<h2 className="text-2xl font-semibold mb-4">页面未找到</h2>
<p className="text-gray-600 mb-6">
抱歉,您访问的页面不存在
</p>
<Link
href="/"
className="px-6 py-2 bg-blue-500 text-white rounded"
>
返回首页
</Link>
</div>
</div>
)
}
触发 not-found
在代码中手动触发 404:
import { notFound } from 'next/navigation'
export default async function PostPage({ params }: { params: { id: string } }) {
const post = await db.post.findUnique({
where: { id: params.id },
})
if (!post) {
notFound() // 触发 not-found.tsx
}
return <div>{post.title}</div>
}
global-error.tsx - 全局错误
global-error.tsx 用于捕获全局错误,它会替换根布局。
基本 global-error.tsx
// app/global-error.tsx
'use client'
export default function GlobalError({
error,
reset,
}: {
error: Error & { digest?: string }
reset: () => void
}) {
return (
<html>
<body>
<div className="min-h-screen flex items-center justify-center">
<div className="text-center">
<h2 className="text-2xl font-bold mb-4">应用错误</h2>
<p className="text-gray-600 mb-6">
发生了严重错误,请刷新页面重试
</p>
<button
onClick={reset}
className="px-6 py-2 bg-blue-500 text-white rounded"
>
刷新页面
</button>
</div>
</div>
</body>
</html>
)
}
嵌套错误处理
Next.js 支持嵌套的错误处理,不同层级可以有不同的错误处理:
app/
├── error.tsx # 全局错误处理
├── dashboard/
│ ├── error.tsx # Dashboard 专属错误处理
│ └── analytics/
│ └── error.tsx # Analytics 专属错误处理
最近层级的 error.tsx 会捕获错误并处理。
API 路由错误处理
API 路由中的错误处理方式不同:
// app/api/posts/route.ts
import { NextResponse } from 'next/server'
import { z } from 'zod'
export async function POST(request: Request) {
try {
const body = await request.json()
// 验证数据
const schema = z.object({
title: z.string().min(1),
content: z.string().min(1),
})
const validatedData = schema.parse(body)
const post = await db.post.create({
data: validatedData,
})
return NextResponse.json(post, { status: 201 })
} catch (error) {
if (error instanceof z.ZodError) {
return NextResponse.json(
{ error: error.errors },
{ status: 400 }
)
}
return NextResponse.json(
{ error: '服务器错误' },
{ status: 500 }
)
}
}
Server Actions 错误处理
Server Actions 的错误处理:
'use server'
import { z } from 'zod'
const PostSchema = z.object({
title: z.string().min(1),
content: z.string().min(1),
})
export async function createPost(formData: FormData) {
const validatedFields = PostSchema.safeParse({
title: formData.get('title'),
content: formData.get('content'),
})
if (!validatedFields.success) {
return {
errors: validatedFields.error.flatten().fieldErrors,
message: '验证失败',
}
}
try {
await db.post.create({
data: validatedFields.data,
})
return { message: '成功' }
} catch (error) {
return { message: '数据库错误' }
}
}
错误日志
记录错误对于调试很重要:
// app/error.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,
digest: error.digest,
stack: error.stack,
}),
})
}, [error])
return (
<div>
<h2>出错了</h2>
<button onClick={reset}>重试</button>
</div>
)
}
实用建议
这里分享几个在错误处理时特别实用的技巧。
提供友好的错误信息
实际开发中,我发现给用户提供友好的错误信息特别重要:
// 推荐这样做 - 友好的错误提示
<div>
<h2>出错了</h2>
<p>请稍后再试或联系客服</p>
<button onClick={reset}>重试</button>
</div>
// 避免这种情况 - 直接显示技术细节
<div>
<pre>{error.stack}</pre>
</div>
记录错误信息
这个小技巧很有用——记录错误信息能帮助你在开发时快速定位问题:
useEffect(() => {
console.error('Error:', error)
// 或者发送到错误追踪服务
}, [error])
提供恢复选项
给用户提供恢复选项,这个小细节能大大提升用户体验:
<button onClick={reset}>重试</button>
<a href="/">返回首页</a>
总结
本节我们学习了 Next.js 的错误处理机制,包括 error.tsx、not-found.tsx、global-error.tsx 等文件的使用。良好的错误处理能提升用户体验,让应用更加健壮。
如果你对本节内容有任何疑问,欢迎在评论区提出来,我们一起学习讨论。