Next.js Not Found 处理
Next.js App Router 用 notFound() 与 not-found.tsx 表达资源不存在这一预期业务状态,将 404 与真正的系统异常清晰分离。
[!info] related notes
- 所属 MOC:
- 前置概念: nextjs-dynamic-route-segments
- 并列概念: nextjs-error-boundaries
- 易混淆概念:
- 关系笔记:
Next.js Not Found 处理
一句话定义
notFound() 与 not-found.tsx 用于表达:请求链路本身正常,但目标资源不存在,因此应该进入 404 语义,而不是把它当成未处理异常。
最小例子
import { notFound } from "next/navigation"
export default async function Page({
params,
}: {
params: Promise<{ goalId: string }>
}) {
const { goalId } = await params
const goal = await getGoal(goalId)
if (!goal) {
notFound()
}
return <GoalDetail goal={goal} />
}
配合:
goals/
└── [goalId]/
├── page.tsx
└── not-found.tsx
就可以给不存在的 Goal 提供专门 404 UI。
Expected Absence 与 Exceptional Failure
这是这篇笔记最重要的语义区分。
资源不存在
数据库查询成功
↓
结果 = null
↓
notFound()
↓
not-found.tsx
这属于:
Expected absence / resource not found
系统异常
数据库连接失败
↓
未处理异常
↓
error.tsx
这属于:
Unexpected failure
因此:
404 ≠ 500
Not Found ≠ Error
为什么不要直接 throw new Error("not found")
错误写法:
if (!goal) {
throw new Error("Goal not found")
}
这会把一个正常、可预期的资源状态伪装成系统故障。
后果可能包括:
- 错误监控产生噪声
- 用户看到错误恢复 UI,而不是明确的 404
- 代码失去 HTTP / 领域语义
- 运维把正常访问不存在资源误判成服务异常
所以:
Expected absence should remain expected.
notFound() 是控制流
notFound() 不只是返回一个布尔值。调用它以后,当前 route segment 的正常渲染流程会终止,并切换到对应的 Not Found UI。
因此代码可以直接写:
if (!goal) {
notFound()
}
return <GoalDetail goal={goal} />
不需要再手工写 return null。
与动态路由的关系
Not Found 在动态路由中特别常见:
/goals/[goalId]
/users/[userId]
/posts/[slug]
URL 中存在一个合法格式的参数,不代表对应资源真实存在。
所以:
Dynamic Segment
↓
params.goalId
↓
validate / query
↓
exists ? Page : notFound()
这和 Next.js 动态路由段 一起构成完整资源访问模型。
404 与授权不要混淆
有些系统为了避免泄露资源是否存在,会对“无权访问某资源”也返回 404,而不是 403。
这属于安全/产品策略,不能简单理解成:
没权限 = 一定 notFound()
授权判断仍然是独立问题。Route 结构、资源存在性和权限语义必须分开:
URL parameter
≠ resource exists
≠ current user may access it
与 error.tsx 的关系
Next.js Error Boundaries 负责未处理异常和局部故障隔离;本笔记负责资源不存在的 404 语义。
最短对比:
DB timeout / code failure
→ error.tsx
query succeeds, entity missing
→ notFound()
→ not-found.tsx
常见误区
所有 null 都应该 notFound()
不一定。
如果 null 表示“用户还没填写昵称”“可选关联不存在”,它可能只是正常字段状态,而不是整个 Route 的资源不存在。
只有当:
当前请求的核心资源不存在,页面本身不再有成立基础
才更适合进入 Not Found。
404 不是“服务器坏了”
它是一种明确的资源定位结果,不应该和系统异常使用同一套 UI、告警和恢复策略。
最短记忆方式
notFound()
= expected resource absence
→ not-found.tsx
→ 404 semantics
它主要回答:请求正常,但这个页面所依赖的核心资源不存在时,应该怎样准确表达?