33- Intercepting Routes

image-20250911103500514

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.

concept

image-20250911103547960

image-20250911103558479

下面是一个使用场景:

image-20240911192739396

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。

下面是另一个使用场景:

image-20240911193641198

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

conventions,本意是约定,在本课程中用的很多。可以理解为nextjs为我们准备好的解决办法,只需要按照nextjs的规定或者说约定来编写代码即可,这样nextjs就知道我们在做什么。

image-20240907191026269

注意:,这里面的on the same level,one level above,这些怎么理解?学习了下面的课程之后,你会知道,这里的level指的是intercepting routes和原本的route的位置关系。比如说下面的第一个案例,指的就是f2和(.)f2这两个文件夹的位置关系处于on the same level。也就是要intercepted的文件夹和原本的文件夹的位置关系。

下面我们用案例来说明上面的各种用法。

1、(.)

在app下创建文件夹和page.tsx文件,编写组件。f1 is short for folder one,依此类推。

image-20240911195625027

查看效果:

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.

注意:代码写完之后,要重启项目才能看到效果。

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.

image-20250911105908325

学了很久都不知道这个功能到底起什么作用?直到老师说了上面这句话,有一点懂了。我问了一下grok,给出了instagram的例子,是这样用的:模态窗口保持了主页的上下文,URL 可分享,且直接访问时显示完整页面,与拦截路由的设计目标一致。用户在photolist里面随便点击一张图片,显示的就是/(.)photo里面的内容,但是如果用户把这张图片分享出去,别的用户访问的就是/photo里面的内容。

这样的好处是什么呢?就是用户在photolist里面点击了图片之后,还能保持住原来的上下文,你也知道,如果用户点击之后,回到原页面、并且维持住原页面用户原有的状态,是非常麻烦的。

 

2、(..)

这个案例的意思是什么呢?如果我们在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文件夹中创建文件夹和文件。

image-20250911112633916

查看效果:

可以看到点击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.

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 .

3、(..)(..)

to match segments two levels above

in this example, we are going to intercept the f4 route when we navigating from f2.

f1同层级创建f4文件夹。

这次我们要从/f1/f2跳转到/f4,所以需要在f2文件夹中创建Link,并且创建intercepting routes,由于f2文件夹里面到f4文件夹隔了两层,所以用到了(..)(..)

4、(...)

to match segments from the root app directory

在app下面创建f5/page.tsx

/f1/f2下面新建一个inner-f2文件夹,编写文件,可以导航到/f5

目标就是在导航到/f5时,拦截这个路由。怎么做呢?inner-f2里面的文件夹与f5文件夹的层级相隔3层,nextjs中没有特定的3层表示方法,最后一种只能拦截到app根目录下的路由,这里就使用(...)f3来表示。

小结:

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。

34- Parallel Intercepting Routes

这一节课来实现上节课说的photo feed功能。

image-20250911120343966

image-20250911120418577

image-20250911120556312

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

 

image-20250911120746737

如果别人直接使用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共同组成页面。这是最重要的一个难点。

这里先把思路搞清楚就行了,可以照着老师的代码实现。

35-Route Handlers

学习到这里,我应该感到高兴,因为我终于把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

image-20250911145823873

image-20250911143350061

image-20250911144032067

与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。

浏览器输入/hello,查看效果。

可以看到,返回了Response里面的文字。

几点注意事项

1、与page routes一样,route handlers must live in the app folder.

image-20250911150225977

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

image-20250911151437883

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

image-20250911151939864

这部分我感觉有点多余,因为我后面学习了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

💥 你会瞬间得到爆炸一样的报错:

这种 Bug 非常隐蔽且极难排查。


❗ 2. 安全重大漏洞 —— 你的数据库逻辑直接暴露给前端

如果你直接调用 prisma:

然后从组件里:

初期看起来是“内部调用”,但实际上:

  • 没有 API 层
  • 没有权限层
  • 没有身份验证层
  • 没有输入校验
  • 直接裸奔操作数据库

现在想象一个场景:

一个用户只要能在浏览器控制台中 import 你的 server action 就可以调用你的数据库操作。

如果把这个暴露出去,你的数据库就变成了:

这是最严重的安全漏洞之一。


❗ 3. 无 API 无法提供给客户端 / 移动端 / 其他服务调用

如果你把逻辑写在组件内部:

那:

  • 你想用 React Native?
  • 你想提供给外部后台?
  • 你想做 admin panel?
  • 你想让移动端访问?
  • 或者需要接口给第三方服务?

抱歉,你:

❌ 没接口 ❌ 没 route handler ❌ 不能跨平台使用数据库逻辑

你的项目会“只能被 Next.js 页面使用”,完全封死扩展性。


❗ 4. component = view 层,不应该包含 data 层(违背所有架构原则)

干净架构里:

  • component → UI 层(view)
  • server actions / route handlers → application 层
  • service / repository → domain 层

你把 Prisma 调到视图层,就是把所有层混在一起:

🟥 视图层 🟥 业务层 🟥 数据层 🟥 权限层

这是彻底不可维护的架构。


那么:我应该如何组织结构?

推荐最佳架构(非常清晰):

流转方式:

这样你就获得:

  • 安全(权限、认证都在 API 层)
  • 清晰职责
  • 更强扩展性(移动端/第三方也能调用)
  • 代码更清洁
  • 更好测试
  • 更利于团队协作

❓ 那不用 API / Route Handlers 就不行吗?

不完全。

如果只在 Server Action 中调用服务层,是可以的:

允许

不允许(永远不要这样):


📌 总结(最重要的三句话)

1. 你不能在客户端使用 Prisma。

一旦泄漏到 browser 就会错误或泄漏数据库。

2. API 层(Route Handlers)负责权限、安全、校验,是必须存在的。

3. 组件只能是 UI 层,不应该直接碰数据库。

36-Handling GET Requests

首先安装Thunder Client插件,用于测试api。

image-20250911152336701

创建app/comments/data.ts,这是准备的数据源。

创建comments/api/route.ts

在thunder client里面测试接口。

image-20250911154050908

37-Handling POST Requests

我们定义的handler函数名称,一定要和具体的HTTP动作相同,比如说GET、POST这些,一定要一致。

新增POST handler函数。

测试OK。

image-20250911154952916

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

image-20250911155036344

38-Dynamic Route Handlers

在获取详情、删除、编辑的时候,需要用户提供id,可能是query或者params的方式,这样就需要在route.ts中定义的时候,先定义一个参数来接收,下面学习怎么定义。

the dynamic route handlers follow the same pattern as dynamic page routes

首先要创建一个[id]文件夹,里面创建/api/route.js,使用params参数来接收具体的id值。

测试:

image-20250911161857954

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

查看了一下官方文档,发现有解决办法。

image-20250911162215213

如果遇到需要获取动态参数的时候,可以直接使用ctx来获取,但是此时列表获取接口也是GET,可以怎么区分开来吗?

问了一下grok,可以通过判断参数来区分:

image-20250911163429716

但是这里不使用这种方式,而是通过文件夹来处理。这一种方法我认为比较好。

那么我就这样做了,原来app/comments里面所有的api文件夹全部删除,改成下面这样。

image-20250911171129800

效果很好啊:

这里其实我有一个疑问,这个接口定义在了/comments/[id]文件夹里面,那么如果列表页面要用这些接口怎么办呢?

这其实很简单,接口就是一些链接,我直接调用就行了。不是我所想的,一个接口定义在哪个文件夹里面,只有这个文件夹里面才能使用,这肯定是不对的。

39-Handling PATCH Requests

文档里面把API说明的很清楚,各种请求都有哪些参数,怎么获取用户传递过来的参数都说明白了:https://nextjs.org/docs/app/api-reference/file-conventions/route#request-body

40-Handling DELETE Requests

41-URL Query Parameters

通过案例来说。需求是:按照给定的?keyword=first这种关键词搜索条件来返回结果。

request.nextUrl.searchParams返回所有的query参数,然后通过get()方法,来获取具体参数的值。

42-Headers in Route Handlers

image-20250911194839110

image-20250911194933178

新建一个app/profile/route.ts文件,来模拟接收headers和设置headers该怎么做。

1、使用request.headers来接收headers

image-20250911200519850

2、使用next/headers提供的headers方法来接收headers

image-20250911200815100

3、在返回结果时,设置headers的信息

需要在浏览器中查看,可以看到,返回的结果被当作html渲染了,而不再只是字符串。

image-20250911201145563

43-Cookies in Route Handlers

image-20250911201311052

我们想在/profile这个接口里面设置cookie,这样这个接口之后的请求都会带上cookie。

先请求一遍:

image-20250911202031555

从此Response Headers里面就带上了cookie,之后的请求就可以使用下面的方法获取了。

1、使用request.cookie.get()方法,来获取具体的cookie的值。

image-20250911202401447

2、使用next/headers提供的cookies方法,有set、get等多种功能。

image-20250911202609955

44-Redirects in Route Handlers

老师用一个案例来说明,原来设计的接口地址是/api/v1/users,现在有了更好的接口/api/v2/users,在前端不更改请求地址的情况下,后端怎么将/api/v1/users的访问重定向到/api/v2/users

使用next/navigation提供的redirect方法,没有错,就是之前在page routes里面学习的redirect方法。

image-20250911203707604

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

45-Caching in Route Handlers

image-20250911204232810

缓存只在GET请求中起作用。

设置缓存效果很简单,只需要在route.ts里面加上export const dynamic = "force-static";。如果想要隔一段时间就能够获取新数据,可以设置export const revalidate = 10;,这里的单位是s

新建app/time/route.ts

可以看到,每次刷新的时候,都会返回最新的时间:

添加了缓存之后,只在生产环境看得到,所以需要先npm run build,然后npm run start查看。

添加缓存:

可以看到,无论怎么刷新,返回的都是第一次请求返回的数据。

如果我想隔一段时间就可以更新一次,有些数据只是变得比较少,不是一直不变,所以还是很常见的需求,该怎么做呢?设置更新时间间隔:

可以看到,隔一段时间之后就可以更新了,注意这里的时间不需要特别精确。

46-Middleware

image-20250911205819650

image-20250912095438969

image-20250912095455486

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

image-20250912102738440

一个项目只能有一个middleware.ts文件,但是还是可以使用js的modules来组织代码。这个文件与app文件夹是同层级的。

案例说明:imagine a scenario where navigating to /profile should redirect to / .当用户访问 /profile的时候,重定向到/

创建/src/middleware.ts文件。

1、使用 config 来配置中间件起作用的路径

2、使用 request.nextUrl.pathname 来判断是否是需要中间件起作用的路径。这里将重定向路径更换了一下,看一下效果。

中间件还可以操作headers和cookies,以下是一个案例:

可以看到上面的中间件并没有设置config,所以cookies和headers是应用到所有接口了。

image-20250912104124629

image-20250912104143026

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

image-20250912095229891