Next.js第十课 - 错误处理

0 阅读4分钟

前面我们学习了很多功能性的内容,本节来聊聊错误处理。错误处理是构建健壮应用的关键,好的错误处理不仅能提升用户体验,还能让调试更容易。

错误处理概述

Next.js App Router 提供了多种错误处理方式:

  1. error.tsx - 运行时错误
  2. not-found.tsx - 404 错误
  3. global-error.tsx - 全局错误
  4. 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 有几个重要特性需要注意:

  1. 必须是客户端组件('use client')
  2. 只捕获运行时错误,不捕获构建时错误
  3. 替换出错的子树
  4. 提供重置功能

自定义错误页面

一个更友好的错误页面:

// 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 等文件的使用。良好的错误处理能提升用户体验,让应用更加健壮。

如果你对本节内容有任何疑问,欢迎在评论区提出来,我们一起学习讨论。