NextJS学习【路由篇】
CLI、App Router路由、后端路由处理程序、中间件
构建工具:Turbopack
React Compiler
Next CLI
next dev
自动具有热加载、错误报告
next build

Size:导航到该路由时下载的资源大小,每个路由的大小只包括它自己的依赖项
First Load JS:加载该页面时下载的资源大小
First load JS shared by all:所有路由共享的 JS 大小会被单独列出来
生产环境开启 React profiler:next build –profile
next build –debug
next start
启动生产环境服务器
Next路由模式 App Router

定义路由(Routes)
定义页面(Pages)
定义布局(Layouts)
布局是指多个页面共享的 UI。在导航的时候,布局会保留状态、保持可交互性并且不会重新渲染,比如用来实现后台管理系统的侧边导航栏。
同一文件夹下如果有 layout.js 和 page.js,page 会作为 children 参数传入 layout。换句话说,layout 会包裹同层级的 page。
根布局(Root Layout)
布局支持嵌套,最顶层的布局称之为根布局(Root Layout),也就是 app/layout.js。它会应用于所有的路由。除此之外,这个布局还有点特殊。
使用 create-next-app 默认创建的 layout.js 代码如下:
1 | // app/layout.js |
app 目录必须包含根布局,也就是 app/layout.js 这个文件是必需的。
根布局必须包含 html 和 body标签,其他布局不能包含这些标签。如果你要更改这些标签,不推荐直接修改
你可以使用路由组创建多个根布局。
默认根布局是服务端组件,且不能设置为客户端组件。
定义模版(Templates)
模板类似于布局,它也会传入每个子布局或者页面。但不会像布局那样维持状态。
模板在路由切换时会为每一个 children 创建一个实例。这就意味着当用户在共享一个模板的路由间跳转的时候,将会重新挂载组件实例,重新创建 DOM 元素,不保留状态。
template.js 代码如下:
1 | // app/template.js |
Layouts和Templates最大的区别就是状态的保持。如果同一目录下既有 template.js 也有 layout.js,最后的输出效果如下:
1 | <Layout> |
layout 会包裹 template,template 又会包裹 page。
某些情况下,模板会比布局更适合:
依赖于 useEffect 和 useState 的功能,比如记录页面访问数(维持状态就不会在路由切换时记录访问数了)、用户反馈表单(每次重新填写)等
更改框架的默认行为,举个例子,布局内的 Suspense 只会在布局加载的时候展示一次 fallback UI,当切换页面的时候不会展示。但是使用模板,fallback 会在每次路由切换的时候展示
Layout VS Template
布局和模板的特点就是:
- 布局嵌套:支持多层布局嵌套,构建复杂的页面结构
- 状态管理:布局会在页面切换时保持状态,而模板会重新渲染
- 根布局:app/layout.tsx 是必须存在的根布局文件
- 渲染顺序:当布局和模板同时存在时,渲染顺序为 layout → template → page
定义加载界面(Loading UI)
实现Loading UI的几种方式
- 使用 page.js导出一个 async 函数
- React use函数
定义错误处理(Error Handling)
其实现借助了 React 的 Error Boundary 功能。简单来说,就是给 page.js 和 children 包了一层 ErrorBoundary。
定义 404 页面(不存在的路由)
关于 app/not-found.js 一定要说明一点的是,它只能由两种情况触发:
- 当组件抛出了 notFound 函数的时候
- 当路由地址不匹配的时候
只需要在 app 目录下新建一个 not-found.js
链接和导航
在 Next.js 中,有 4 种方式可以实现路由导航:
- 使用 组件
- 使用 useRouter Hook(客户端组件)
- 使用 redirect 函数(服务端组件)
- 使用浏览器原生 History API
导航行为设置
App Router 的默认行为是滚动到新路由的顶部,或者在前进后退导航时维持之前的滚动距离。
如果你想要禁用这个行为,你可以给 组件传递一个 scroll={false}属性,或者在使用 router.push和 router.replace的时候,设置 scroll: false:
1 | // next/link |
1 | // useRouter |
动态路由
使用动态路由,需要将文件夹的名字用方括号括住,比如 [id]、[slug]。这个路由的名字会作为 params prop 传给布局、 页面、 路由处理程序 以及 generateMetadata 函数。
[folderName]
当你访问 /blog/a的时候,params 的值为 { slug: ‘a’ }。
当你访问 /blog/yayu的时候,params 的值为 { slug: ‘yayu’ }。
[…folderName]
在命名文件夹的时候,如果在方括号内添加省略号,比如 […folderName],这表示捕获所有后面所有的路由片段。
当你访问 /shop/a的时候,params 的值为 { slug: [‘a’] }。
当你访问 /shop/a/b的时候,params 的值为 { slug: [‘a’, ‘b’] }。
当你访问 /shop/a/b/c的时候,params 的值为 { slug: [‘a’, ‘b’, ‘c’] }。
[[…folderName]]
它与上一种的区别就在于,不带参数的路由也会被匹配(就比如 /shop)
路由组
在 app目录下,文件夹名称通常会被映射到 URL 中,但可以将文件夹标记为路由组,阻止文件夹名称被映射到 URL 中。
使用路由组,可以将路由和项目文件按照逻辑进行分组,但不会影响 URL 路径结构。路由组可用于比如:
- 按站点、意图、团队等将路由分组
- 在同一层级中创建多个布局,甚至是创建多个根布局
那么该如何标记呢?把文件夹用括号括住就可以了,就比如 (dashboard)。
按逻辑分组

创建不同布局
借助路由组,即便在同一层级,也可以创建不同的布局:
创建多个根布局

创建多个根布局,需要删除掉 app/layout.js 文件,然后在每组都创建一个 layout.js文件。创建的时候要注意,因为是根布局,所以要有 和
这个功能很实用,比如将前台购买页面和后台管理页面都放在一个项目里,一个 C 端,一个 B 端,两个项目的布局肯定不一样,借助路由组,就可以轻松实现区分。
平行路由
类似Vue的插槽功能
平行路由跟路由组一样,不会影响 URL
独立路由处理
- 使用平行路由可以将单个布局拆分为多个插槽,使代码更易于管理,尤其适用于团队协作的时候
- 每个插槽都可以定义自己的加载界面和错误状态,比如某个插槽加载速度比较慢,那就可以加一个加载效果,加载期间,也不会影响其他插槽的渲染和交互。当出现错误的时候,也只会在具体的插槽上出现错误提示,而不会影响页面其他部分,有效改善用户体验
- 每个插槽都可以有自己独立的导航和状态管理,这使得插槽的功能更加丰富,比如在上面的例子中,我们在 @analytics 插槽下又建了查看页面 PV 的 /page-views、查看访客的 /visitors,使得同一个插槽区域可以根据路由显示不同的内容
用途1:条件渲染
用途2:子导航
default.js
为了解决这个问题,Next.js 提供了 default.js。当发生硬导航的时候,Next.js 会为不匹配的插槽呈现 default.js 中定义的内容,如果 default.js 没有定义,再渲染 404 错误。
硬导航影响,软导航不变
拦截路由
https://nextjs-app-route-interception.vercel.app/
同样一个路由地址,却展示了不同的内容。这就是拦截路由的效果。如果你在 dribbble.com 想要访问 dribbble.com/shots/xxxxx,此时会拦截 dribbble.com/shots/xxxxx 这个路由地址,以 Modal 的形式展现。而当直接访问 dribbble.com/shots/xxxxx 时,则是原本的样式。
简单的来说,就是希望用户继续停留在重要的页面上。比如上述例子中的图片流页面,开发者肯定是希望用户能够持续在图片流页面浏览,如果点击一张图片就跳转出去,会打断用户的浏览体验,如果点击只展示一个 Modal,分享操作又会变得麻烦一点。拦截路由正好可以实现这样一种平衡。又比如任务列表页面,点击其中一项任务,弹出 Modal 让你能够编辑此任务,同时又可以方便的分享任务内容。
实现方式
- (.) 表示匹配同一层级
- (..) 表示匹配上一层级
- (..)(..) 表示匹配上上层级。
- (…) 表示匹配根目录
路由处理程序(网络请求)
使用next框架做接口开发 – route handler
定义路由处理程序
route.js
该文件必须在 app目录下,可以在 app 嵌套的文件夹下,但是要注意 page.js和 route.js不能在同一层级同时存在。
想想也能理解,page.js和 route.js本质上都是对路由的响应。page.js主要负责渲染 UI,route.js主要负责处理请求。如果同时存在,Next.js 就不知道用谁的逻辑了。
支持的请求方法
Next.js 支持 GET、POST、PUT、PATCH、DELETE、HEAD 和 OPTIONS 这些 HTTP 请求方法。如果传入了不支持的请求方法,Next.js 会返回 405 Method Not Allowed。
1 | // route.js |
实现一个Get请求
1 | import { NextResponse } from 'next/server' |
实现一个Post请求
1 | export async function POST(request) { |
传入参数
每个请求方法的处理函数会被传入两个参数,一个 request,一个 context 。两个参数都是可选的:
- request (optional)
使用request对象,可以快捷的读取 cookies 和处理 URL。
1 | export async function GET(request, context) { |
- context (optional)
context 只有一个值就是 params,它是一个包含当前动态路由参数的对象。
1 | // app/dashboard/[team]/route.js |
当访问 /dashboard/1 时,params 的值为 { team: ‘1’ }
app/shop/[tag]/[item]/route.js
/shop/1/2
{ tag: ‘1’, item: ‘2’ }
app/blog/[…slug]/route.js
/blog/1/2
{ slug: [‘1’, ‘2’] }
缓存行为
默认缓存
退出缓存
这些情况都会导致退出缓存:
- GET 请求使用 Request 对象
- 使用其他 HTTP 方法,比如 POST
- 路由段配置项手动声明为动态模式
1 | export const dynamic = 'force-dynamic' |
设置缓存
除了退出缓存,也可以设置缓存的时效,适用于一些重要性低、时效性低的页面。
有两种常用的方案,一种是使用路由段配置项。
1 | export const revalidate = 10 |
这句代码的效果并不是设置服务器每 10s 会自动更新一次 /api/time。而是最少 10s 后才重新验证。
举个例子,假设现在访问了 /api/time,此时时间设为 0s,10s 内持续访问,/api/time返回的都是之前缓存的结果。当 10s 过后,假设你第 12s 又访问了一次 /api/time,此时虽然超过了 10s,但依然会返回之前缓存的结果,但同时会触发服务器更新缓存,当你第 13s 再次访问的时候,就是更新后的结果。
简单来说,超过 revalidate 设置时间的首次访问会触发缓存更新,如果更新成功,后续的返回就都是新的内容,直到下一次触发缓存更新。
中间件
使用中间件,可以拦截并控制应用里的所有请求和响应。
比如可以基于传入的请求,重写、重定向、修改请求或响应头、甚至直接响应内容。一个比较常见的应用就是鉴权,在打开页面渲染具体的内容前,先判断用户是否登录,如果未登录,则跳转到登录页面。
中间件示例
写中间件,你需要在项目的根目录定义一个名为 middleware.js的文件:
1 | // middleware.js |
设置匹配路径
matcher配置项
第一种是使用 matcher配置项,示例代码如下:
1 | export const config = { |
matcher 不仅支持字符串形式,也支持数组形式,用于匹配多个路径:
1 | export const config = { |
matcher 还可以判断查询参数、cookies、headers
1 | export const config = { |
条件语句
1 | import { NextResponse } from 'next/server' |
中间件逻辑
设置和读取cookies
用法跟路由处理程序一致,使用 NextRequest 和 NextResponse 快捷读取和设置 cookies。
1 | import { NextResponse } from 'next/server' |
这里调用了 NextResponse.next() 这个方法,这个方法专门用在 middleware 中,因为中间件进行一层处理后,返回的结果还要在下一个逻辑中继续使用,此时就需要返回 NextResponse.next()
设置和读取headers
1 | // middleware.js |
设置Cors
1 | import { NextResponse } from 'next/server' |
直接响应
1 | import { NextResponse } from 'next/server' |
执行顺序
- headers(next.config.js)
- redirects(next.config.js)
- 中间件 (rewrites, redirects 等)
- beforeFiles (next.config.js中的rewrites)
- 基于文件系统的路由 (public/, _next/static/, pages/, app/ 等)
- afterFiles(next.config.js中的rewrites)
- 动态路由 (/blog/[slug])
- fallback中的 (next.config.js中的rewrites)
注: beforeFiles 顾名思义,在基于文件系统的路由之前,afterFiles顾名思义,在基于文件系统的路由之后,fallback顾名思义,垫底执行。