
There are two advanced routing patterns in nextjs, we've learned parallel routes in previous lessons, in the next two lessons we'll learn intercepting routes.


下面是一个使用场景:

click on a login button, normally takes users to a full login page.However, with intercepting routes you can configure the application to display a login modal instead while the URL is still updated to reflect the /login route.This ensures the link remains sharable.If the page is reloaded or if someone accesses the page using the shared link, the full login page is displayed as expected.
这个场景是说,如果用户点击login按钮或链接,可以直接给一个modal框,但此时的路由地址仍然显示/login,这样对于用户操作来说避免了跳转,体验会比较好。但如果用户reloaed此页面或者直接访问/login,则会直接展示fullpage。
这是一种设计模式,告诉我们遇到这种需求的时候,就要想到使用intercepting routes。
下面是另一个使用场景:

similarly in a photo Feed application, where users can browse through a list of images, click on a photo would navigate users to a new page dedicated to that image.
With intercepting routes, clicking on a photo opens a modal within a feed, displaying an enlarged photo and details like the photographer's name and location.The URL updates to reflect the selected photo ensuring it is sharable.Direct URL access or page-reloads still lead to a full page view of the photo for complete details.
photo Feed application:照片供稿应用。
这里以照片供稿应用程序来说明,当点击一个照片时,会出现一个modal,里面显示放大的照片,并且显示照片的部分信息。此时的URL是当前照片的详情地址,这样可以在用户直接访问或者page-reolad的时候,显示full-page。这样做的好处就是直接在当前页面显示放大的照片,让用户在放大浏览照片的时候,可以记清楚自己浏览到哪里了,提升了用户体验。
conventions,本意是约定,在本课程中用的很多。可以理解为nextjs为我们准备好的解决办法,只需要按照nextjs的规定或者说约定来编写代码即可,这样nextjs就知道我们在做什么。

注意:,这里面的on the same level,one level above,这些怎么理解?学习了下面的课程之后,你会知道,这里的level指的是intercepting routes和原本的route的位置关系。比如说下面的第一个案例,指的就是f2和(.)f2这两个文件夹的位置关系处于on the same level。也就是要intercepted的文件夹和原本的文件夹的位置关系。
下面我们用案例来说明上面的各种用法。
在app下创建文件夹和page.tsx文件,编写组件。f1 is short for folder one,依此类推。

xxxxxxxxxx151// routing-demo/src/app/f1/page.tsx23import Link from 'next/link'45export default function F1() {6 return (7 <>8 <h1>F1 page</h1>9 <div>10 <Link href="/f1/f2">F2</Link>11 </div>1213 </>14 )15}xxxxxxxxxx51// routing-demo/src/app/f1/f2/page.tsx23export default function F2() {4 return <h1>F2 page</h1>5}查看效果:

let's think about 2 key elements, the source folder and the target folder, we want to intercept(拦截) the f2 route when we navigating from f1 route.so f1 is our source folder and f2 is our target folder.
next, we'll set up an intercepting route to change this behavior.At the same level, we use a dot within parentheses prefix in a folder name.So in f1 directory, we create a new folder, dot within parentheses, and the folder name is f2.

within this new folder, create a page.tsx file with the default exported react component.
xxxxxxxxxx51// routing-demo/src/app/f1/(.)f2/page.tsx23export default function InterceptedF2() {4 return <h1>(.) Intercepted F2 page</h1>5}注意:代码写完之后,要重启项目才能看到效果。

when you click on the F2 link now, the url updates to /f1/f2,but the intercepted routes content is displayed instead.reloading the page, we can see the original f2 content.

学了很久都不知道这个功能到底起什么作用?直到老师说了上面这句话,有一点懂了。我问了一下grok,给出了instagram的例子,是这样用的:模态窗口保持了主页的上下文,URL 可分享,且直接访问时显示完整页面,与拦截路由的设计目标一致。用户在photolist里面随便点击一张图片,显示的就是/(.)photo里面的内容,但是如果用户把这张图片分享出去,别的用户访问的就是/photo里面的内容。
这样的好处是什么呢?就是用户在photolist里面点击了图片之后,还能保持住原来的上下文,你也知道,如果用户点击之后,回到原页面、并且维持住原页面用户原有的状态,是非常麻烦的。
xxxxxxxxxx101app/2├── feed/3│ ├── page.js // 图片流页面(/feed)4│ ├── photo/5│ │ └── [postId]/6│ │ └── page.js // 实际的 /feed/p/[postId] 页面7│ └── (.)photo/8│ └── [postId]/9│ └── page.js // 拦截 /feed/p/[postId] 的模态窗口10├── layout.js
这个案例的意思是什么呢?如果我们在f1页面中要访问f3的页面,但是f3实际上是一个独立的功能,不需要或者不能放到f1文件夹中去,那么我们可以在f1文件夹里面使用(..)f3来做intercepting routes,后面的几种类型,(..)(..)和(...)都是这样的意思,所代表的就是目标文件夹与source folder的位置关系。
It is possible to match segments one level above.The convention is to prefix the folder name with two dots within parentheses.
在f1文件夹中创建文件夹和文件。

xxxxxxxxxx51// routing-demo/src/app/f3/page.tsx23export default function F3() {4 return <h1>F3 page</h1>5}xxxxxxxxxx171// routing-demo/src/app/f1/page.tsx23import Link from "next/link"45export default function F1() {6 return (7 <>8 <h1>F1 page</h1>910 <div>11 <Link href={`/f1/f2`}>F2</Link>12 <br />13 <Link href={`/f3`}>F3</Link>14 </div>15 </>16 )17}查看效果:

可以看到点击F3按钮,直接跳转到/f3的完整页面。现在我们想使用intercepting routes,改变跳转的效果。
inside the f1 folder, create a f3 folder with two dots inside parentheses as prefix, then create a page.tsx file with a simple react component.

xxxxxxxxxx51// routing-demo/src/app/f1/(..)f3/page.tsx23export default function InterceptedF3() {4 return <h1>(..) Intercepted F3 page</h1>5}
in the /f1 route ,when you click the F3 link, the URL still shows /f3 , but you will see the intercepted content instead.when you reload the page, you'll see the original F3 content .
to match segments two levels above
in this example, we are going to intercept the f4 route when we navigating from f2.
f1同层级创建f4文件夹。
xxxxxxxxxx41// routing-demo/app/f4/page.tsx2export default function F4() {3 return <h1>F4 page</h1>4}这次我们要从/f1/f2跳转到/f4,所以需要在f2文件夹中创建Link,并且创建intercepting routes,由于f2文件夹里面到f4文件夹隔了两层,所以用到了(..)(..)。
xxxxxxxxxx131// routing-demo/app/f1/f2/page.tsx2import Link from "next/link"34export default function F2() {5 return (6 <>7 <h1>F2 page</h1>8 <div>9 <Link href={`/f4`}>F4</Link>10 </div>11 </>12 )13}xxxxxxxxxx41// routing-demo/app/f1/f2/(..)(..)f4/page.tsx2export default function InterceptedF4(){3 return <h1>(..)(..) InterceptedF4 page</h1>4}
to match segments from the root app directory
在app下面创建f5/page.tsx。
xxxxxxxxxx41// app/f5/page.tsx2export default function F5() {3 return <h1>F5 page</h1>4}在/f1/f2下面新建一个inner-f2文件夹,编写文件,可以导航到/f5。
xxxxxxxxxx141// routing-demo/app/f1/f2/inner-f2/page.tsx23import Link from "next/link"45export default function InnerF2() {6 return (7 <>8 <h1>Inner F2 page</h1>9 <div>10 <Link href={`/f5`}>F5</Link>11 </div>12 </>13 )14}目标就是在导航到/f5时,拦截这个路由。怎么做呢?inner-f2里面的文件夹与f5文件夹的层级相隔3层,nextjs中没有特定的3层表示方法,最后一种只能拦截到app根目录下的路由,这里就使用(...)f3来表示。
xxxxxxxxxx41// routing-demo/app/f1/f2/inner-f2/(...)f5/page.tsx2export default function InterceptedF5() {3 return <h1>(...) InterceptedF5 page</h1>4}
小结:
intercepting routes allow you to load a route from another part of your application within the current layout.This routing paradigm is useful when you want to display the content of a route without the user to switching to a different context.
我有一个问题,比如说我在f1,使用了拦截路由跳转到f2,f2是一个弹窗,那么我在关闭弹窗的时候,应该怎么做呢?
因为此时的路由地址是
f1/f2,我肯定加上路由返回的功能(并不是简单的关闭弹窗就可以的),此时f1页面的context还能保持住吗?试一下,在f1里面加一个input看一下。答案是不能保持住。那怎么办?下节课会讲到。使用parallel routes + intercepting routes。
这一节课来实现上节课说的photo feed功能。



注意看,点击了图片之后,url地址栏也改变了。并且这种特性还能让我们使用导航栏的箭头返回原页面。

如果别人直接使用URL访问,或者reload page,会显示一个照片的详情界面。
实现步骤是什么呢?简单来讲就是:
1、创建一个photo-feed路由,里面的page.tsx展示photolist。
2、photolist里面的每一个photo项,都使用Link包裹,点击会跳转到photo-feed/1这样的路由,此时需要考虑拦截路由。
首先需要使用/photo-feed/[photoId]/page.tsx,那么这种情况的拦截路由该怎么做呢?其实在上一节课的f1/f2案例里面可以看到,创建了/f1/(.)f2/page.tsx这样的文件,就是拦截路由。
这里的情况,应该这样做:/photo-feed/(.)[photoId]/page.tsx,接下来就是编写组件了。
3、但其实还差一步,差哪一步呢?就是怎么显示Modal的问题。不要把vue里面的内容带进来了,vue里面的Modal根本就没有使用到路由,就是使用的组件。这里是需要路由跳转的。
案例里面的要求应该是,点击图片跳转到了/photo-feed/1这种页面之后,原来的页面是不变的,Modal虽然说可以做成overlay的样式,但是从/photo-feed跳转到/photo-feed/1,怎么保证/photo-feed/page.tsx的内容仍然显示?并且保持原有内容不变。
这是一个重大问题,从33节里面的动图可以看到,虽然我拦截了路由,并且显示了拦截路由的内容。但是跳转过来的原始页面的内容呢?不在了。

所以这里要使用parallel routes,将/@modal/(.)[photoId]/page.tsx当作一个组件传进photo-feed/layout.tsx里面,与children共同组成页面。这是最重要的一个难点。
xxxxxxxxxx151// teacher-code/Next.js-15-Tutorials/routing-demo/src/app/photo-feed/layout.tsx23import "./styles.css";45export default function Layout(props: {6 modal: React.ReactNode;7 children: React.ReactNode;8}) {9 return (10 <>11 {props.modal}12 {props.children}13 </>14 );15}这里先把思路搞清楚就行了,可以照着老师的代码实现。
学习到这里,我应该感到高兴,因为我终于把nextjs里面涉及到前端的路由部分学习完了。回想起来,内容其实不多,但是思维方式的转变很巨大,一段时间不复习,真的会彻底忘记。
接下来开始学习route handlers,“route handlers let us build restful endpoints with complete control over the response”,可以让我们不用另外做一个前后端分离的服务端。
新建一个route-handlers-demo的项目,npx create-next-app route-handlers-demo。
https://nextjs.org/docs/app/getting-started/route-handlers-and-middleware



与page routes一样,route handlers must live in the app folder.
创建app/hello文件夹,并创建route.ts文件,这个文件名是固定的。here we export a function that matches the GET http verb。
xxxxxxxxxx51// route-handlers-demo/src/app/hello/route.ts23export async function GET(){4 return new Response("Hello world!")5}浏览器输入/hello,查看效果。

可以看到,返回了Response里面的文字。
几点注意事项:
1、与page routes一样,route handlers must live in the app folder.

2、route.js不能和page.js处于同一级,因为访问/文件夹名的时候,给出的是route.js的返回结果,页面不会显示。所以一般在page.js的同级创建api文件夹,将route.js放到api文件夹里面去。访问接口的时候,/hello/api就可以访问了。


3、不只是folder可以有自己的api/route.js,里面的subfolders也可以有自己的api/route.js。访问的时候就按照folder的顺序来访问即可。


这部分我感觉有点多余,因为我后面学习了prisma连接数据库,我可以直接调用写好的方法接口来操作数据。还要写接口,这不是多余吗?
答案是不行,因为prisma是跑在服务端的,是不能用在client components里面的,这一点非常隐蔽。只有做成了route handlers之后,调用起来才会没有问题。并且我忘记了一个重要的点:caching in route handlers,route handlers里面是有缓存的,这一点对于响应速度来说非常重要。
❌ 直接在前端组件里调用 Prisma 的问题(非常致命)
❗ 1. Node 端 vs 浏览器端的问题 —— 你不能在浏览器里使用 Prisma
Prisma 是一个 Node.js 数据库客户端。
它不能、也不会在浏览器里运行。
如果你在 React server component 中调用 Prisma,你以为自己在 “组件里”,实际上 Next.js 把它跑在 服务器端。
但是只要你稍微不注意,比如:
- 写了
"use client"- 或者一个组件从 server component 变成 client component
- 或者被某个库强制转成 client component
💥 你会瞬间得到爆炸一样的报错:
xxxxxxxxxx11PrismaClient is unable to run in the browser.这种 Bug 非常隐蔽且极难排查。
❗ 2. 安全重大漏洞 —— 你的数据库逻辑直接暴露给前端
如果你直接调用 prisma:
xxxxxxxxxx31export async function createUser(data) {2return prisma.user.create3}然后从组件里:
xxxxxxxxxx11const user = await createUser()初期看起来是“内部调用”,但实际上:
- 没有 API 层
- 没有权限层
- 没有身份验证层
- 没有输入校验
- 直接裸奔操作数据库
现在想象一个场景:
一个用户只要能在浏览器控制台中 import 你的 server action 就可以调用你的数据库操作。
如果把这个暴露出去,你的数据库就变成了:
xxxxxxxxxx11任何人都能直接调用——不需要 API、不需要 token、不需要权限控制这是最严重的安全漏洞之一。
❗ 3. 无 API 无法提供给客户端 / 移动端 / 其他服务调用
如果你把逻辑写在组件内部:
xxxxxxxxxx11prisma.post.findMany()那:
- 你想用 React Native?
- 你想提供给外部后台?
- 你想做 admin panel?
- 你想让移动端访问?
- 或者需要接口给第三方服务?
抱歉,你:
❌ 没接口 ❌ 没 route handler ❌ 不能跨平台使用数据库逻辑
你的项目会“只能被 Next.js 页面使用”,完全封死扩展性。
❗ 4. component = view 层,不应该包含 data 层(违背所有架构原则)
干净架构里:
- component → UI 层(view)
- server actions / route handlers → application 层
- service / repository → domain 层
你把 Prisma 调到视图层,就是把所有层混在一起:
🟥 视图层 🟥 业务层 🟥 数据层 🟥 权限层
这是彻底不可维护的架构。
那么:我应该如何组织结构?
推荐最佳架构(非常清晰):
xxxxxxxxxx71/src2/app3/api4/users/route.ts ← API / Route handlers5/services/user.service.ts6/repositories/user.repo.ts7/components/*.tsx流转方式:
xxxxxxxxxx71React Component2↓ Server Action 或 fetch3Route Handler (API)4↓5Service Layer(业务逻辑)6↓7Repository(Prisma)这样你就获得:
- 安全(权限、认证都在 API 层)
- 清晰职责
- 更强扩展性(移动端/第三方也能调用)
- 代码更清洁
- 更好测试
- 更利于团队协作
❓ 那不用 API / Route Handlers 就不行吗?
不完全。
如果只在 Server Action 中调用服务层,是可以的:
✔ 允许:
xxxxxxxxxx41// Server Action2export async function createUserAction(form: FormData) {3return await userService.create()4}❌ 不允许(永远不要这样):
xxxxxxxxxx21"use client"2prisma.user.create(...)📌 总结(最重要的三句话)
1. 你不能在客户端使用 Prisma。
一旦泄漏到 browser 就会错误或泄漏数据库。
2. API 层(Route Handlers)负责权限、安全、校验,是必须存在的。
3. 组件只能是 UI 层,不应该直接碰数据库。
首先安装Thunder Client插件,用于测试api。

创建app/comments/data.ts,这是准备的数据源。
xxxxxxxxxx161// `app/comments/data.ts`23export const comments = [4 {5 id: 1,6 text: "This is the first comment",7 },8 {9 id: 2,10 text: "This is the second comment",11 },12 {13 id: 3,14 text: "This is the third comment",15 },16];创建comments/api/route.ts
xxxxxxxxxx51import { comments } from "../data";23export async function GET() {4 return Response.json(comments)5}在thunder client里面测试接口。

我们定义的handler函数名称,一定要和具体的HTTP动作相同,比如说GET、POST这些,一定要一致。
新增POST handler函数。
xxxxxxxxxx231// route-handlers-demo/src/app/comments/api/route.ts2import { comments } from "../data";34export async function GET() {5 return Response.json(comments)6}78export async function POST(request: Request) {9 // 使用 request 来接收传过来的数据10 const comment = await request.json();11 // 新增一个评论12 const newComment = {13 id: comments.length + 1,14 text: comment.text15 }1617 comments.push(newComment);18 // 新增的时候,返回201比较好。这个先不管19 return new Response(JSON.stringify(newComment), {20 headers: { "content-Type": "application/json" },21 status: 20122 })23}测试OK。

再请求一下GET,发现有4条数据了。

在获取详情、删除、编辑的时候,需要用户提供id,可能是query或者params的方式,这样就需要在route.ts中定义的时候,先定义一个参数来接收,下面学习怎么定义。
the dynamic route handlers follow the same pattern as dynamic page routes
首先要创建一个[id]文件夹,里面创建/api/route.js,使用params参数来接收具体的id值。
xxxxxxxxxx221// route-handlers-demo/src/app/comments/[id]/api/route.ts23import { comments } from "../../data";45/**6 * 7接收两个参数,一个是request,一个是params,这个params是 context object containing the route parameters。8也就是说,request里面可以获取json请求数据,而params上有param这种请求数据910这里由于不需要使用request参数,所以前面加上了_,变成了_request,提示typescript这里不用管11 */1213export async function GET(14 _request: Request,15 params: Promise<{ id: string }>16) {17 // 从params参数获取到url上的param传参18 const { id } = await params;19 const comment = comments.find(c => c.id === parseInt(id))2021 return Response.json(comment)22}测试:

为什么报错了,因为这种url是不能获取到param的,我看到老师是没有使用api文件夹的,当然可以成功。但是真实项目中肯定会很复杂,怎么办呢?
查看了一下官方文档,发现有解决办法。

如果遇到需要获取动态参数的时候,可以直接使用ctx来获取,但是此时列表获取接口也是GET,可以怎么区分开来吗?
问了一下grok,可以通过判断参数来区分:

但是这里不使用这种方式,而是通过文件夹来处理。这一种方法我认为比较好。
xxxxxxxxxx121app/2├── api/3│ ├── comments/4│ │ ├── route.ts5│ │ └── [id]/6│ │ └── route.ts7├── comments/8│ ├── (.)[id]/9│ │ └── page.js // 拦截 /comments/[id] 的模态窗口10│ ├── [id]/11│ │ └── page.js // 渲染 /comments/[id] 的页面12│ └── page.js
那么我就这样做了,原来app/comments里面所有的api文件夹全部删除,改成下面这样。

xxxxxxxxxx171// route-handlers-demo/src/app/api/comments/[id]/route.ts23import { comments } from "@/app/comments/data";4import type { NextRequest } from 'next/server'56// 这里的两个参数是按照官方文档来做的78export async function GET(9 _req: NextRequest,10 ctx: RouteContext<'/api/comments/[id]'>11) {12 // 从params参数获取到url上的param传参13 const { id } = await ctx.params;14 const comment = comments.find(c => c.id === parseInt(id))1516 return Response.json(comment)17}效果很好啊:

这里其实我有一个疑问,这个接口定义在了
/comments/[id]文件夹里面,那么如果列表页面要用这些接口怎么办呢?这其实很简单,接口就是一些链接,我直接调用就行了。不是我所想的,一个接口定义在哪个文件夹里面,只有这个文件夹里面才能使用,这肯定是不对的。
文档里面把API说明的很清楚,各种请求都有哪些参数,怎么获取用户传递过来的参数都说明白了:https://nextjs.org/docs/app/api-reference/file-conventions/route#request-body。
xxxxxxxxxx301// route-handlers-demo/src/app/api/comments/[id]/route.ts2import { comments } from "@/app/comments/data";3import type { NextRequest } from 'next/server'45// 这里的两个参数是按照官方文档来做的67export async function GET(8 _req: NextRequest,9 ctx: RouteContext<'/api/comments/[id]'>10) {11 // 从params参数获取到url上的param传参12 const { id } = await ctx.params;13 const comment = comments.find(c => c.id === parseInt(id))1415 return Response.json(comment)16}1718export async function PATCH(19 req: NextRequest,20 ctx: RouteContext<'/api/comments/[id]'>21) {22 const { id } = await ctx.params;23 const body = await req.json();// 使用 req.json() 获取body里面的数据,官方文档里面有24 const { text } = body2526 const index = comments.findIndex(c => c.id === parseInt(id))27 comments[index].text = text;2829 return Response.json(comments[index])30}
xxxxxxxxxx161// route-handlers-demo/src/app/api/comments/[id]/route.ts23import { comments } from "@/app/comments/data";4import type { NextRequest } from 'next/server'56export async function DELETE(7 _req: NextRequest,8 ctx: RouteContext<'/api/comments/[id]'>9) {10 const { id } = await ctx.params;11 const index = comments.findIndex(c => c.id === parseInt(id))12 const deletedComment = comments[index]1314 comments.splice(index, 1)15 return Response.json(deletedComment)16}
通过案例来说。需求是:按照给定的?keyword=first这种关键词搜索条件来返回结果。
request.nextUrl.searchParams返回所有的query参数,然后通过get()方法,来获取具体参数的值。
xxxxxxxxxx111// route-handlers-demo/src/app/api/comments/route.ts23import { comments } from "@/app/comments/data";4import type { NextRequest } from "next/server";56export async function GET(request: NextRequest) {7 const searchParams = request.nextUrl.searchParams;8 const keyword = searchParams.get("keyword")9 const filteredComments = keyword ? comments.filter(c => c.text.includes(keyword)) : comments;10 return Response.json(filteredComments)11}


新建一个app/profile/route.ts文件,来模拟接收headers和设置headers该怎么做。
1、使用request.headers来接收headers
xxxxxxxxxx101// app/profile/route.ts23import type { NextRequest } from "next/server";45export async function GET(request: NextRequest) {6 const requestHeaders = new Headers(request.headers);7 console.log("request.headers = ", requestHeaders.get("Authorization"));89 return new Response("Profile API data")10}
2、使用next/headers提供的headers方法来接收headers
xxxxxxxxxx101// app/profile/route.ts23import { headers } from "next/headers";45export async function GET() {6 const headersList = await headers();7 console.log("headersList = ", headersList.get("Authorization"));89 return new Response("Profile API data")10}
3、在返回结果时,设置headers的信息
xxxxxxxxxx151// app/profile/route.ts23import { headers } from "next/headers";45export async function GET() {6 const headersList = await headers();7 console.log("headersList = ", headersList.get("Authorization"));89 return new Response("<h1>Profile API data</h1>", {10 headers: {11 // 将返回结果的类型改为 text/html12 "content-Type": "text/html"13 }14 })15}需要在浏览器中查看,可以看到,返回的结果被当作html渲染了,而不再只是字符串。



我们想在/profile这个接口里面设置cookie,这样这个接口之后的请求都会带上cookie。
xxxxxxxxxx121// app/profile/route.ts23export async function GET() {4 5 return new Response("<h1>Profile API data</h1>", {6 headers: {7 // 将返回结果的类型改为 text/html8 "content-Type": "text/html",9 "Set-Cookie": "theme=dark"10 }11 })12}先请求一遍:

从此Response Headers里面就带上了cookie,之后的请求就可以使用下面的方法获取了。
1、使用request.cookie.get()方法,来获取具体的cookie的值。
xxxxxxxxxx151// app/profile/route.ts23import { NextRequest } from "next/server"45export async function GET(request: NextRequest) {6 const theme = request.cookies.get("theme")78 console.log("通过request获取cookie", theme);9 return new Response("<h1>Profile API data</h1>", {10 headers: {11 // 将返回结果的类型改为 text/html12 "content-Type": "text/html"13 }14 })15}
2、使用next/headers提供的cookies方法,有set、get等多种功能。
xxxxxxxxxx161// app/profile/route.ts23import { cookies } from "next/headers";45export async function GET() {6 const cookieStore = await cookies();7 cookieStore.set("resultPage", "20");8 9 console.log("通过cookies方法获取", cookieStore.get("resultPage"));10 return new Response("<h1>Profile API data</h1>", {11 headers: {12 // 将返回结果的类型改为 text/html13 "content-Type": "text/html"14 }15 })16}
老师用一个案例来说明,原来设计的接口地址是/api/v1/users,现在有了更好的接口/api/v2/users,在前端不更改请求地址的情况下,后端怎么将/api/v1/users的访问重定向到/api/v2/users。
使用next/navigation提供的redirect方法,没有错,就是之前在page routes里面学习的redirect方法。

当然,v2的接口要注意包含v1接口的全部字段,否则有错误。

缓存只在GET请求中起作用。
设置缓存效果很简单,只需要在route.ts里面加上export const dynamic = "force-static";。如果想要隔一段时间就能够获取新数据,可以设置export const revalidate = 10;,这里的单位是s。
新建app/time/route.ts
xxxxxxxxxx71// app/time/route.ts23export async function GET() {4 return Response.json({5 time: new Date().toLocaleTimeString()6 })7}可以看到,每次刷新的时候,都会返回最新的时间:

添加了缓存之后,只在生产环境看得到,所以需要先npm run build,然后npm run start查看。
添加缓存:
xxxxxxxxxx91// app/time/route.ts23export const dynamic = "force-static";45export async function GET() {6 return Response.json({7 time: new Date().toLocaleTimeString()8 })9}
可以看到,无论怎么刷新,返回的都是第一次请求返回的数据。
如果我想隔一段时间就可以更新一次,有些数据只是变得比较少,不是一直不变,所以还是很常见的需求,该怎么做呢?设置更新时间间隔:
xxxxxxxxxx101// app/time/route.ts23export const dynamic = "force-static";4export const revalidate = 10;// 设置重新检验的时间,这里设置10秒之后就可以检验新老数据是否一致,如果不一致就更新56export async function GET() {7 return Response.json({8 time: new Date().toLocaleTimeString()9 })10}
可以看到,隔一段时间之后就可以更新了,注意这里的时间不需要特别精确。



中间件通过 specify paths (具体说明路径) 的方式,来指定在哪里生效。有两种具体说明paths的方式,以案例说明。

一个项目只能有一个middleware.ts文件,但是还是可以使用js的modules来组织代码。这个文件与app文件夹是同层级的。
案例说明:imagine a scenario where navigating to /profile should redirect to / .当用户访问 /profile的时候,重定向到/。
创建/src/middleware.ts文件。
1、使用 config 来配置中间件起作用的路径
xxxxxxxxxx131// /src/middleware.ts2import { NextResponse } from "next/server";3import type { NextRequest } from 'next/server';45// 定义并暴露中间件6export function middleware(request: NextRequest) {7 return NextResponse.redirect(new URL("/", request.url))8}910// 告诉nextjs,在 /profile 路径应用这个中间件11export const config = {12 matcher: "/profile"13}
2、使用 request.nextUrl.pathname 来判断是否是需要中间件起作用的路径。这里将重定向路径更换了一下,看一下效果。
xxxxxxxxxx111// /src/middleware.ts23import { NextResponse } from "next/server";4import type { NextRequest } from 'next/server';56// 定义并暴露中间件7export function middleware(request: NextRequest) {8 if (request.nextUrl.pathname === "/profile") {9 return NextResponse.redirect(new URL("/hello", request.nextUrl))10 }11}
中间件还可以操作headers和cookies,以下是一个案例:
xxxxxxxxxx201// src/middleware.ts23import { NextResponse } from "next/server";4import type { NextRequest } from 'next/server';56// 定义并暴露中间件7export function middleware(request: NextRequest) {8 // 获取到response对象9 const response = NextResponse.next();10 // 获取cookies11 const theme = request.cookies.get("theme")12 // 如果没有theme这个cookie,就设置13 if (!theme) {14 response.cookies.set("theme", "dark")15 }16 // 设置headers 17 response.headers.set("custom-header", "custom-value")1819 return response;20}可以看到上面的中间件并没有设置config,所以cookies和headers是应用到所有接口了。


学习到这里,就结束了nextjs里面route相关的内容,总结一下,主要学习了这些内容:
