Next.js App Router 约定式文件实战:loading、error、not-found 怎么兜住加载态与异常
Next.js App Router 约定式文件实战:loading、error、not-found 怎么兜住加载态与异常用 Next.js App Router 写页面,你迟早会遇到这三个问题:页面里await fetch(...)拉数据时,用户盯着一片空白,不知道是在加载还是卡死了。接口挂了或抛异常,整个页面直接白屏崩掉,还可能把错误堆栈暴露给用户。用户访问了一个不存在的资源(比如/posts/99999),你想给个像样的 404 页,而不是默认那个丑页面。很多人第一反应是在组件里手写isLoading、try/catch、if (!data) return NotFound/。但 App Router 提供了约定式文件——在路由目录里放特定文件名,框架自动帮你接管加载态、错误态和 404。这篇把loading.tsx、error.tsx、not-found.tsx三个一次讲透。朴素写法:所有状态挤在一个组件里先看不用约定式文件时,一个详情页大概长这样:// app/posts/[id]/page.tsx —— 反面教材 use client; import { useEffect, useState } from react; export default function PostPage({ params }: { params: { id: string } }) { const [post, setPost] useState(null); const [loading, setLoading] useState(true); const [error, setError] useState(null); useEffect(() { fetch(/api/posts/${params.id}) .then(r { if (!r.ok) throw new Error(加载失败); return r.json(); }) .then(setPost) .catch(setError) .finally(() setLoading(false)); }, [params.id]); if (loading) return p加载中.../p; if (error) return p出错了/p; if (!post) return p找不到文章/p; return articleh1{post.title}/h1p{post.body}/p/article; }问题很明显:业务渲染只有最后一行,前面全是状态判断的噪音;而且被迫写成客户端组件(use client),丢掉了服务端组件直接await数据的能力。App Router 的约定式文件就是来拆掉这些样板的。loading.tsx:自动加载态,基于 Suspense在路由目录放一个loading.tsx,Next.js 会自动用它包一层 Suspense。当同级page.tsx(服务端组件)在await数据时,先渲染loading.tsx的内容,数据好了再换成真实页面:// app/posts/[id]/loading.tsx export default function Loading() { // 骨架屏,比加载中...体验好得多 return ( div classNameanimate-pulse space-y-4 div classNameh-8 w-2/3 bg-gray-200 rounded / div classNameh-4 w-full bg-gray-200 rounded / div classNameh-4 w-5/6 bg-gray-200 rounded / /div ); }有了它,page.tsx就能回归纯粹的服务端组件,直接await,不用管 loading:// app/posts/[id]/page.tsx —— 服务端组件,直接 await export default async function PostPage({ params, }: { params: Promise{ id: string }; }) { const { id } await params; // Next.js 15 起 params 是 Promise const res await fetch(https://api.example.com/posts/${id}); const post await res.json(); return ( article h1{post.title}/h1 p{post.body}/p /article ); }关键理解:loading.tsx的本质就是给page.tsx套了Suspense fallback{Loading/}。所以它对「路由跳转时的加载」和「组件 await 期间」都生效,你什么都不用手写。error.tsx:兜住渲染异常,必须是客户端组件如果page.tsx里的 fetch 抛了异常,或者渲染过程报错,同级的error.tsx会接住它,展示降级 UI,而不是整页白屏:// app/posts/[id]/error.tsx use client; // ← error.tsx 必须是客户端组件,这是硬性要求 import { useEffect } from react; export default function Error({ error, reset, }: { error: Error { digest?: string }; reset: () void; // 调它会重新渲染这段路由,相当于重试 }) { useEffect(() { // 上报到监控系统,别把错误吞了 console.error(页面渲染出错:, error); }, [error]); return ( div classNamep-6 text-center h2 出了点问题/h2 p classNametext-gray-500{error.message}/p button onClick{() reset()} classNamemt-4 px-4 py-2 bg-blue-600 text-white rounded 重试 /button /div ); }有两个坑要记牢:坑一:error.tsx必须加use client。它内部用 React 错误边界实现,错误边界只能是客户端组件。忘了加,构建直接报错。坑二:error.tsx抓不到同级layout.tsx的错误。因为 error 边界包在 layout 内部,layout 本身抛错它管不着。要兜住 layout 的错误,得在上一级目录放error.tsx,或者用global-error.tsx兜住根 layout。reset()函数是 App Router 特有的:调用它会尝试重新渲染出错的这段路由子树,给用户一个「重试」而不用刷新整页。not-found.tsx notFound():优雅的 404资源不存在时,别手动return div404/div。App Router 提供了notFound()函数,调用它会立即中断渲染,并渲染最近的not-found.tsx:// app/posts/[id]/page.tsx import { notFound } from next/navigation; export default async function PostPage({ params, }: { params: Promise{ id: string }; }) { const { id } await params; const res await fetch(https://api.example.com/posts/${id}); if (res.status 404) { notFound(); // ← 中断渲染,跳到 not-found.tsx,后面代码不会执行 } const post await res.json(); return articleh1{post.title}/h1p{post.body}/p/article; }// app/posts/[id]/not-found.tsx import Link from next/link; export default function NotFound() { return ( div classNamep-6 text-center h2文章不存在/h2 p classNametext-gray-500你要找的文章可能已被删除。/p Link href/posts classNametext-blue-600 underline 返回文章列表 /Link /div ); }notFound()的原理是抛出一个特殊错误,被框架捕获后渲染not-found.tsx,同时响应状态码正确返回 404——这点对 SEO 很重要,搜索引擎知道这是不存在的页面,不会收录。手写div404/div的话状态码还是 200,搜索引擎会以为是正常页。它们的嵌套关系:一张图理清这几个约定式文件不是平级的,它们的包裹顺序是固定的。同一个路由段里,Next.js 大致这样嵌套:layout error ← 兜住下面的渲染错误 suspense fallback{loading} ← loading 兜住加载态 not-found 边界 page ← 你的实际页面 /not-found /suspense /error /layout从这张图能推出几条实用结论:loading和error都定义在路由段上,子路由可以有自己的一套,就近生效。error在layout内层,所以管不到同级 layout 的错误(要放上一级)。not-found既能被notFound()主动触发,也会兜住未匹配的路由。小结App Router 的三个约定式文件,把过去挤在组件里的状态处理拆成了框架职责:loading.tsx:自动 Suspense fallback,让page.tsx回归纯服务端组件直接await,写骨架屏体验最佳。error.tsx:错误边界,必须use client;提供reset()做重试;抓不到同级 layout 的错误,要放上一级。not-found.tsxnotFound():优雅 404,且返回正确的 404 状态码,对 SEO 友好;notFound()会中断后续渲染。记忆点:别再手写isLoading / try-catch / 404 判断——在路由目录里放对文件名,框架自动帮你套好 Suspense 和错误边界。

相关新闻

GD32 EXMC驱动LCD:从硬件配置到GUI集成的嵌入式显示系统开发

GD32 EXMC驱动LCD:从硬件配置到GUI集成的嵌入式显示系统开发

1. 从“点灯”到“刷屏”:为什么需要EXMC来驱动LCD? 如果你玩过GD32F303这类Cortex-M4内核的MCU,点亮一个LED或者驱动一个简单的1602字符液晶,大概率是直接用GPIO模拟时序或者用SPI、I2C这类串行接口。这在小尺寸、低分辨率的屏幕…

2026/10/27 2:05:56 阅读更多 →
Kafka如何保证「消息不丢失」,「顺序传输」,「不重复消费」,以及为什么会发生重平衡(reblanace)

Kafka如何保证「消息不丢失」,「顺序传输」,「不重复消费」,以及为什么会发生重平衡(reblanace)

Kafka 如何保证「消息不丢失」「顺序传输」「不重复消费」,以及重平衡(Rebalance)原理详解 Kafka 作为分布式消息队列的标杆,在金融、电商、日志采集等场景中被广泛使用。但在生产环境中,我们经常会遇到三个核心问题&a…

2026/10/27 3:54:40 阅读更多 →
i.MX6ULL启动流程全解析:从ROM Code到Linux内核的嵌入式系统引导

i.MX6ULL启动流程全解析:从ROM Code到Linux内核的嵌入式系统引导

1. 项目概述:从按下电源到系统就绪的旅程 搞嵌入式开发,尤其是像NXP i.MX6ULL这种应用处理器,最基础也最绕不开的一个环节就是启动流程。这东西就像你电脑开机时BIOS到Windows加载的过程,只不过在资源受限的嵌入式世界里&#xff…

2026/10/26 13:17:21 阅读更多 →

最新新闻

Electron桌面应用开发实战:从环境搭建到打包发布全流程

Electron桌面应用开发实战:从环境搭建到打包发布全流程

做前端这些年,我大部分时间都在跟浏览器标签页打交道,直到第一次把桌面应用跑起来,才发现原来自己一直被限制在地址栏和刷新按钮围起来的框框里。Electron这个词,最开始是在某段时间频繁出现在同事的讨论里,一个用HTML…

2026/10/26 12:00:21 阅读更多 →
滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

简介:亚太天能T109滑盖智能锁产品说明书是一份PDF格式的官方技术文档,主要面向智能锁安装人员、终端用户及售后维护者,帮助快速掌握产品安装、配置与常见故障处理。全包仅1个PDF文件,大小4.37MB,内容结构清晰&#xff…

2026/10/27 4:06:49 阅读更多 →
PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

上个月我在调试一台高速AGV的驱动系统,整车要求空载跑到8m/s,电机转速已经拉到额定点,驱动器的输出电压也顶到了母线电压极限附近。再往上踩速度指令,转速纹丝不动,三相电流反而开始震荡。那会儿我的第一反应就是&…

2026/10/27 7:21:53 阅读更多 →
C++核心知识点:从内存管理到STL的实战指南

C++核心知识点:从内存管理到STL的实战指南

C 这门语言我前前后后折腾了十几年,用它写过嵌入式驱动、做过游戏服务端,也搭过基础组件库。每次看到新人一头扎进"C 核心知识点"这个搜索词,我其实挺感慨的——这门语言的难点从来不是语法多,而是知识点之间的耦合太深…

2026/10/26 12:00:21 阅读更多 →
T109滑盖智能锁怎么装?安装调试与故障排查全攻略

T109滑盖智能锁怎么装?安装调试与故障排查全攻略

简介:亚太天能T109滑盖智能锁产品说明书以PDF格式发布,适合智能锁用户、安装人员与售后维修人员阅读。内容围绕T109系列指纹密码刷卡锁展开,先介绍前面板、后面板、锁体、机械钥匙、电池盒盖等部件名称与功能,再给出完整安装步骤&…

2026/10/27 6:20:47 阅读更多 →
LLM知识降熵:从信息垄断到理解权下放

LLM知识降熵:从信息垄断到理解权下放

你有没有过这种经历:翻开源码注释、论文摘要或者一份专业报告,每个字都认识,连在一起却像天书。想找人聊聊,身边要么没人懂,要么懂的懒得跟你解释。后来我试着把同一段内容丢给一个大语言模型,几秒钟之后&a…

2026/10/27 4:06:50 阅读更多 →

日新闻

周新闻

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/26 12:00:19 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/27 7:21:50 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/26 8:34:37 阅读更多 →