Next.js 错误处理:自定义错误页面和错误边界
Next.js 错误处理:自定义错误页面和错误边界
作者:码力无边
在理想的世界里,我们的代码永远不会出错,API 永远可用,用户也总是会访问存在的页面。但在现实世界中,意外无处不在:用户可能会输入一个错误的 URL,服务器可能会暂时宕机,组件可能会因为无效数据而渲染失败。
一个专业的应用程序与一个业余项目的区别,很大程度上在于它如何处理这些“意外情况”。一个未经处理的错误会导致应用崩溃或显示一个冰冷、丑陋的错误信息,这会严重损害用户信任和体验。
Next.js App Router 提供了一套精细且强大的文件约定,让我们能够优雅地捕获和处理不同类型的错误,为用户提供友好的反馈,并帮助开发者快速定位问题。
本文将深入探讨两种核心的错误处理机制:
- 处理路由未找到 (404) 错误:通过
not-found.tsx
文件。 - 处理运行时错误:通过
error.tsx
文件(React 错误边界)。
1. 捕获 404 Not Found 错误
当用户访问一个不存在的 URL 时(例如 /posts/this-slug-does-not-exist
),服务器应该返回一个 404 状态码,并显示一个“页面未找到”的提示页面。
a) 创建全局 404 页面
最简单的方式是在 app
目录的根级别创建一个 not-found.tsx
文件。这个文件将会作为你整个应用的默认 404 页面。
app/not-found.tsx
import Link from 'next/link';export default function NotFound() {return (<div style={{ textAlign: 'center', marginTop: '50px' }}><h1>404 - 页面未找到</h1><p>抱歉,您要查找的页面不存在。</p><Link href="/"><a>返回首页</a></Link></div>);
}
工作原理:当 Next.js 无法为请求的 URL 匹配到任何现有的路由段时,它会自动渲染这个 app/not-found.tsx
组件,并返回 404 HTTP 状态码。
b) 主动触发 404
在某些情况下,URL 路径本身是有效的,但其对应的数据不存在(例如,一个有效的博客 slug,但数据库中没有这篇文章)。在这种情况下,我们需要在服务端主动触发一个 404 状态。
Next.js 提供了一个 notFound()
函数来实现这一点。
app/posts/[slug]/page.tsx
import { notFound } from 'next/navigation';
import { getPostBySlug } from '@/lib/api';export default async function PostPage({ params }: { params: { slug: string } }) {const post = await getPostBySlug(params.slug);// 如果文章不存在,则调用 notFound()if (!post) {notFound();}return (<article><h1>{post.title}</h1><p>{post.content}</p></article>);
}
工作原理:当在 Server Component 中调用 notFound()
函数时,Next.js 会终止当前页面的渲染,并向上遍历组件树,直到找到最近的 not-found.tsx
文件并渲染它。如果找不到局部的,就会使用全局的 app/not-found.tsx
。
2. 处理运行时错误 - error.tsx
not-found.tsx
专门处理“未找到”的情况。但对于其他所有类型的运行时错误(例如,API 调用失败、组件内部抛出异常、数据格式不正确等),我们需要一个更通用的解决方案。这就是 error.tsx
的用武之地。
error.tsx
文件利用了 React 错误边界 (Error Boundary) 的概念。它是一个特殊的 UI 组件,可以:
- 捕获其子组件树中发生的任何 JavaScript 错误。
- 渲染一个降级 (fallback) UI,而不是让整个应用崩溃。
- 记录错误信息。
a) 创建错误边界
你可以在任何路由段(即任何文件夹)内创建一个 error.tsx
文件,它将自动成为该路由段及其所有子路由的错误边界。
重要:error.tsx
文件必须是一个客户端组件 ("use client"
)。因为错误处理和恢复本身就是一种交互式的、依赖状态的行为。
app/dashboard/error.tsx
"use client";import { useEffect } from 'react';// error.tsx 接收两个 props
export default function DashboardError({error,reset,
}: {error: Error & { digest?: string };reset: () => void;
}) {useEffect(() => {// 你可以在这里将错误信息上报给监控服务,如 Sentry, LogRocketconsole.error(error);}, [error]);return (<div><h2>Dashboard 出错了!</h2><p>抱歉,加载仪表盘时发生了未知错误。</p><buttononClick={// 尝试重新渲染该路由段() => reset()}>再试一次</button></div>);
}
工作原理与特性:
- 作用域:
app/dashboard/error.tsx
会包裹app/dashboard/page.tsx
和该目录下的任何子页面。它不会捕获同级或上级layout.tsx
中发生的错误。这意味着,即使 Dashboard 页面崩溃了,你的根布局(如导航栏和页脚)依然会正常显示,提供了很好的隔离性。 error
prop:一个标准的 JavaScriptError
对象,包含了错误的message
和stack
。在服务端错误中,它还会包含一个digest
属性,这是一个错误的哈希值,可以用于在服务端日志中关联和查找具体错误。reset
prop:一个函数。调用它会触发 Next.js 尝试重新渲染错误边界内的组件树。如果错误是暂时的(例如,一次网络抖动),这个功能可以让用户在不刷新整个页面的情况下恢复正常。
b) 全局错误处理
与 not-found.tsx
类似,你也可以在 app
目录的根级别创建一个 error.tsx
,它将作为整个应用的“最后一道防线”,捕获所有未被局部错误边界捕获的错误。
app/error.tsx
(全局错误边界)
"use client";// ... 结构与局部的 error.tsx 相同 ...export default function GlobalError({ error, reset }) {return (<html><body><h2>糟糕,出错了!</h2><p>我们已经记录了这个问题,并会尽快修复。</p><button onClick={() => reset()}>刷新重试</button></body></html>);
}
注意:根 error.tsx
会替换掉整个根 layout.tsx
的内容,所以你需要在这里提供完整的 <html>
和 <body>
结构。
总结与最佳实践
健壮的错误处理是构建生产级应用的关键。Next.js App Router 通过文件约定,将复杂的错误处理模式变得简单而直观。
文件名 | 作用 | 类型 | 核心功能 |
---|---|---|---|
not-found.tsx | 捕获 404 “Not Found” 错误 | 服务端/客户端均可 | 显示“未找到”页面,自动返回 404 状态码。 |
error.tsx | 捕获运行时错误(500 Internal Server Error 等) | 必须是客户端组件 | 作为错误边界,显示降级 UI,提供重试机制。 |
最佳实践:
- 提供一个全局的
app/not-found.tsx
和app/error.tsx
作为基础保障。 - 在关键业务路由段(如
/dashboard
,/checkout
)中创建局部的error.tsx
,以提供更具体、更友好的错误信息和恢复路径。 - 在数据获取函数中主动调用
notFound()
,处理“URL有效但数据不存在”的场景。 - 在
error.tsx
中集成错误监控服务,将被捕获的错误上报,以便开发团队能够主动发现并修复问题。
通过精心设计你的错误处理策略,你不仅能提升应用在面对意外时的稳定性,更能向用户展现出应用的专业和可靠,从而赢得他们的信任。
现在,我们的应用已经变得非常健壮了。在下一篇文章中,我们将进入专栏的倒数第二篇,讨论将我们的应用最终发布到线上的所有环节——部署。我们将对比 Vercel, Netlify 和自托管等多种部署选项。敬请期待!