next 框架学习.md
> 时间:2026/07/17/17:31 ## html 生成时机不同产生的不同渲染方式:CSR、SSR、SSG、ISR ``` HTML 什么时候生成? │ ┌────────────┼────────────┐ │ │ │ ▼ ▼ ▼ 浏览器生成 请求时生成 提前生成 CSR SSR SSG │ ▼ 可以重新生成 │ ▼ ISR ``` - CSR(Client Side Rendering,客户端渲染):Vue、React 创建的普通 SPA 默认采用 CSR。服务器返回一个几乎为空的 HTML(通常只有一个 `root` 节点),浏览器下载并执行 JavaScript 后,React/Vue 才开始渲染页面,因此页面是在客户端生成的。 - SSR(Server Side Rendering,服务端渲染):每次请求页面时,服务器都会重新执行渲染逻辑,生成最新的 HTML 返回给浏览器,因此能够保证数据实时,但每次请求都会产生渲染成本。 - SSG:静态站点生成,前端项目在构建时便生成了静态的html文件,该文件中包含了完整的数据,当网站部署后,服务器直接返回该html,渲染开销小,但可能存在过时问题,因为构建时的数据可能会随着时间过时,但一次构建,一直复用 - ISG(Incremental Static Regeneration):ISR 是 SSG 的扩展。页面仍然可以复用预渲染结果,但 Next.js 可以按照时间或主动失效策略,在部署后重新生成对应页面或缓存内容,而不需要重新构建整个网站。 以上四点并不是孤立的,在不同情况下可以选择不同的方式,trade-off ## app router App Router 将应用组织成一棵 Route Tree。客户端导航时,Next.js 可以复用未变化的共享 Layout,只更新发生变化的路由分支。 `Users`页面: ``` RootLayout │ ├── DashboardLayout │ │ │ └── UsersPage │ └── LoginPage ``` `Orders`页面: ``` RootLayout │ ├── DashboardLayout │ │ │ └── OrdersPage │ └── LoginPage ``` 真正改变的只有 Page,其它组件没有改变,如:`RootLayout` ## 路由:目录结构即是路由 在next中,路由无需特殊配置,项目的目录即是路由 ``` app/ ├── layout.tsx └── dashboard/ ├── layout.tsx └── users/ └── page.tsx ``` 访问url `/dashboard/users/` 便对应到 ``` app/ ├── layout.tsx └── dashboard/ ├── layout.tsx └── users/ └── [page.tsx] ``` `page.tsx` 文件是路由节点的入口文件 ## layout:next自动组装嵌套layout 与vue、react框架不同,next会自动组装layout vue和react需开发者手动编写嵌套的layout: ```vue ``` 这是通过开发者编写代码实现的layout,而next无需如此,在目录进行处理后,next会自动组装layout ``` app/ ├── layout.tsx ├── dashboard/ │ ├── layout.tsx │ └── users/ │ └── page.tsx ``` 访问: ``` /dashboard/users ``` next会自动组装layout,得到 ``` RootLayout ↓ DashboardLayout ↓ UsersPage ``` next会根据目录进行递归和组装,得到url完整的layout: - **递归(Recursion)**:对目录进行递归,层层找到url所关联的layout - **组装(Compose)**:将递归的所找到的layout文件全部组装起来 ## root layout和layout 由于app目录结构即是路由,若存在以下架构: ``` app/ ├── layout.tsx ├── dashboard/ │ ├── layout.tsx │ └── users/ │ └── page.tsx ``` 可以发现有两个layout.tsx,在访问 dashboard/users 路由时,会将两个layout进行自动组装(无需手动配置,next自动完成) 其中 Root Layout 有点特殊,必须返回html和body,因为:Next.js 在服务器端生成完整的 HTML Document,并返回给浏览器,但整个next项目中并没有类似于vue或者react需要创建一个index.html,而html需要两个标签组成:html和body标签,考虑到layout的特殊性,next会进行layout tree 构建,因此,html标签和body标签的任务便交给Root Layout了,在Root Layout中配置html标签和body标签 Root Layout 实际上取代了传统 SPA 中的 `index.html` 和 `App.vue`,既负责生成完整的 HTML Document,又作为整个应用的根布局 当然其它layout.tsx无需配置也不能配置html和body标签,因为,一个html文件只能有一个html标签和body标签 ## Template Template 的出现是因为:layout,在页面切换和跳转时,layout会保留状态和数据,例如: - 在root layout中定义了count - 在users页面将count增加到6 - 切换为orders页面时,count的状态不会重置,而是保持为6 但项目中并不是所有的切换都需要保留状态的,如:路由变化状态需重置,此时便需使用 template,template可以看做layout的特殊版: - template 与 Layout 一样可以包裹子内容 - template 当路由变化时会触发卸载和挂载,而layout则保留状态 next构建Route Tree时template所处位置?——layout的下一层级 ``` Layout └── Template └── Children ``` ## Server Component 和 Client Component Server Component 和 client Component 并不是对前端与后端的区分,而是对组件运行环境的区分 ``` Server Component = 后端代码 ❌ Client Component = 前端代码 ❌ ``` 这是错误的,Server Component 和 client Component 是对前端组件进行划分,由于大部分UI不需要浏览器能力,因此默认放服务器更合理,服务器直接返回渲染后的HTML(类似于SSR),而 client Component 则是需要浏览器运行环境的组件,如:点击、键盘与鼠标监听等事件是需要浏览器运行环境的,此时需指定 client Component next 框架默认组件是 server component,其设计理念是: > 大部分UI不需要浏览器能力,因此默认放服务器更合理 与传统前端的区别是,对前端组件进行了细化,传统前端组件都由浏览器渲染和执行 ```js // vue或react function UserList(){ const [users,setUsers] = useState([]) useEffect(()=>{ fetch("/api/users") .then(...) },[]) return ( ... ) } ``` 流程: ``` 浏览器 ↓ 下载 JavaScript [额外的开销] ↓ 执行 React ↓ useEffect ↓ 请求 API [额外的开销] ↓ 获取数据 ↓ 重新渲染 ``` 而next的server component则将无需浏览器环境的组件进行直接渲染,返回渲染后的组件html代码: ``` 浏览器请求 ↓ Next Server ↓ 执行 Users() ↓ 查询数据库 ↓ 生成 React Server Payload ↓ 生成 HTML ↓ 返回浏览器 ``` 与 vue或react框架的对比是:减少api调用开销,服务器直接查询数据并渲染,将渲染后的html返回给前端 ## client component 依赖传递 在前端开发中,组件并不是独立的,在大多时候是嵌套使用的,此时若父组件为 client component,那么父组件所依赖的所有子组件都会是 client component,但server component并不会 > 疑问:为什么client component的依赖会传递? > > 解答:依赖问题,若父组件是client component,那么父组件必然在浏览器环境运行,但父组件所依赖的子组件呢?——也需满足在浏览器环境运行,否则会出问题,如:子组件只能在server运行(如:数据库查询),那么父组件所依赖的子组件无法在浏览器运行,子组件会在浏览器环境崩溃,进而导致父组件崩溃,因此client component的关系是层层递进的,父->子 推荐方式: Server Component 负责数据获取和页面结构,Client Component 负责交互;即父组件为server component,子组件决定是否为client component ``` Server Component (父) | | ↓ Client Component (子) ``` 困惑:没有理解这一节中的技巧,client component可以通过props接受server component ## 技巧:client component 可通过props接受server component client component 与 server component的核心区别在于: - Client Component 的 import 会形成依赖链,父->子,若父为client component,那么子也为client component - Server Component 可以渲染 Client Component,即server component并不会形成依赖链,子组件可在两种类型中做选择 由于 client component的依赖链,下方的例子是错误的: ```react "use client" import ServerComponent from "./ServerComponent" export default function Client(){ return ( ❌ ) } ``` 父组件为 client component,所导入的子组件仍需为 client component 但,props可以避开client component的依赖链,类似于vue的插槽,插槽并不会在意传入的内容是client component还是server component ```react // Client.tsx "use client" export default function Client({ children }){ return ( {children} ) } ``` 述例子中通过 props 传递参数,`children` 可为server component / client component ## hydration 水合 > 服务器输出的 HTML 像干燥的植物,js运行后为它浇水,重获生命 水合是什么?——以SSR进行说明,SSR用户每次访问网站时会返回服务端渲染完成后的HTML,此时浏览器直接处理渲染完成后的HTML,此时的html是静态的没有交互(js没有下载),并下载js,js下载完成后与html进行绑定,如:按钮的点击事件等,通过js的绑定,让页面具有交互能力,这个过程便是水合 疑问: 1. next为什么需要水合?server component往往处理无需浏览器环境的组件,如详情页只需渲染,没有交互,此时无需水合;client component 组件next会将js发给用户浏览器,浏览器渲染client component的html代码,然后执行js,与传统vue或react相似,不存在接管html的情况,无需水合 > Server Component 不需要 Hydration,因为没有客户端逻辑;Client Component 在 Next.js 中通常先由服务器生成 HTML,再由浏览器 Hydration 接管,而不是简单重新生成 HTML。 2. client component和server component都会渲染html?只是client会将对于的js发给用户浏览器? > Client Component 发送 JS 是为了恢复交互能力,而 Server Component 不发送组件 JS,因此无法在浏览器继续运行。 3. client component与server component的区别只有:是否发生js,渲染都是在server? > 区别包括运行环境、是否发送 JS、是否拥有浏览器能力、是否需要 Hydration。Client Component 可以参与服务器预渲染,但必须能够在浏览器执行。 ## Server Component 数据获取与缓存策略 > Server Component 在服务器运行,可以直接获取数据并将结果渲染到 HTML。Next.js 通过 `fetch` 的缓存配置决定数据是否复用,以及页面采用静态生成、动态渲染还是增量更新策略 对于服务端 next 可选择 SSR、SSG、ISR几种策略,而 server component 无需每次重新请求数据,可对 server component 的请求数据进行缓存,至于缓存策略如何,则由开发者决定 示例: ```js // 默认 Server Component export default async function UsersPage() { const res = await fetch( "https://api.example.com/users", { // 缓存策略 } ) const users = await res.json() return ( {users.map(user => (
{user.name}
))} ) } ```
可在 fetch 请求中通过指定cache实现server component的不同方式:
```js fetch(url, { cache: "no-store" }) ```
- no-store:不缓存,类似于SSR - revalidate: 设置缓存时间,类似于ISR,到时间后缓存失效,重新渲染 - force-cache:强制缓存,类似于SSG,缓存永远不失效
## Route Handler 与 Server Action
### route handler
route handler:Next.js 中创建 HTTP API 的方式,与前后端分离时,两者通信一致,通过http请求获取数据
api路径:
- 文件路径便是api接口,如:文件路径 `app/api/users/route.ts` 所对应的接口便是 `/api/users` - routs.ts 是接口的入口文件
### server action
由于 next 是全栈框架,与前后端分离存在特殊之处,全栈框架中前端可直接调用后端的服务无需经过http请求,而 server action 便是为此存在,可直接内部调用,无需编写一层api
next 组件分为两类: server component 与 client component
- Client Component 负责交互。客户端触发的数据修改可以通过 Server Action 或 Route Handler 调用服务器逻辑 - Server Component 负责页面数据获取和渲染;实际项目通常不建议将业务逻辑和数据库操作直接写在组件中,而是抽离到 Service/Lib 层,server component直接访问 service/lib 层
## 前后端接口划分
- Server Component 负责“读数据并生成页面” - Server Action 负责“用户操作触发服务器业务”,内部调用 - Route Handler 负责“向外提供 HTTP API”,第三方处理
## server action
通过server action,client component可绕过http api接口调用,直接调用后端逻辑,但需要声明action:
- server action需使用 "use serve" 标注 - 建议 server action 不写业务逻辑,而是去调用后端的service - 本质是将http api调用转为server action,需额外进行server action的编写
```js "use server"
import { createUser } from "@/services/user"
export async function register(){
await createUser() // 没有编写具体的业务逻辑,而是去调用后端的services,将http api请求内化
} ```
## redirect 重定向
在next中,没有vue或react的router,而是通过 redirect 实现跳转,如:登录成功跳转面板
`server action`:
```js "use server"
import { redirect } from "next/navigation"
export async function login( formData:FormData ){
const username = formData.get("username")
const password = formData.get("password")
const user = await verifyUser( username, password )
if(!user){
return { error: "账号密码错误" }
}
// 创建session
await createSession(user)
redirect( "/dashboard" )
} ```
## revalidatePath 与 revalidateTag 处理next缓存
在 next 中,缓存的刷新需通过:
| | revalidatePath | revalidateTag | | ---- | -------------- | ------------- | | 依据 | URL | 数据 | | 粒度 | 页面 | 数据 | | 适合 | 简单页面刷新 | 复杂数据共享 |
revalidatePath 处理:url路径缓存,让url路径缓存失效
revalidateTag 处理:接口数据
获取数据:
```js fetch( "https://api.com/products", { next:{ tags:["products"] } } ) ```
更新:
```js revalidateTag("products") ```
所有使用:
``` products ```
数据缓存的位置,都会失效
## middleware 关卡
由于 next 并没有router,那么如何实现路由拦截呢?如:登录后才能访问系统,此时若点击链接直接访问系统的 /dashborad,如何拦截?
1. 为每个页面添加验证:是否登录,登录后才能访问 2. middleware
第一种方法无疑太过繁琐,无法统一管理;而 next 框架提供的 middleware 可以很好的实现拦截,可将 middleware 看做一道关卡,用户访问系统时需经过该关卡,但需注意:
1. middleware 请求频率高:每一次请求都会经过该关卡,不适合数据库查询,100次请求查询100次数据库,耗时且慢 2. middleware 适合快速判断,如:JWT是否有效,cookie是否存在等
那么真正的权限校验应该如何处理?——后端,服务器 service 层处理
## Prisma ORM 数据库操作
在 springboot 中,一般在 mapper 层处理数据库操作,ORM与之类似,Object Relational Mapping:
- object:对象 - Relational:关系,关系型数据库 - Mapping:映射
通过 ORM 可直接将 js 数据直接映射到数据库中,不用直接编写 SQL 语句,而 prisma 便是 ORM 的一种
``` Next.js
↓
Prisma
↓
DataBase ```
## next 项目架构推荐
根目录 `app` + 公共代码 + 业务功能组织:由于 next 框架只识别 app 目录,其它目录不会受到影响
``` project/ ├── src/ │ ├── app/ # 路由、布局、页面组装 │ ├── features/ # 业务功能划分子目录,该业务的后端在对应子目录下实现 │ └── shared/ # 跨业务公共能力,如:公共后端代码 ├── public/ └── package.json ```
核心思想是:
- `app` 只负责路由与页面组装 - `features` 按业务领域组织代码 - 公共组件、工具和基础设施放在业务目录之外
| 目录 | 职责 | | --------------------------- | ---------------------------------- | | `app` | Next.js 路由和入口 | | `features/posts/components` | 文章业务的 UI | | `features/posts/server` | 文章业务的服务端逻辑 | | `shared/server` | 数据库、认证、Redis 等公共基础设施 |
示例:
``` project/ ├── src/ │ ├── app/ │ │ ├── (public)/ │ │ │ ├── page.tsx │ │ │ └── posts/ │ │ │ └── page.tsx │ │ │ │ │ ├── (auth)/ │ │ │ ├── login/ │ │ │ │ └── page.tsx │ │ │ └── register/ │ │ │ └── page.tsx │ │ │ │ │ ├── dashboard/ │ │ │ ├── layout.tsx │ │ │ └── page.tsx │ │ │ │ │ ├── api/ │ │ │ └── posts/ │ │ │ └── route.ts │ │ │ │ │ ├── layout.tsx │ │ ├── loading.tsx │ │ ├── error.tsx │ │ └── globals.css │ │ │ ├── features/ │ │ ├── auth/ │ │ │ ├── components/ │ │ │ ├── server/ │ │ │ │ ├── actions.ts │ │ │ │ ├── service.ts │ │ │ │ └── repository.ts │ │ │ ├── schemas/ │ │ │ └── types.ts │ │ │ │ │ └── posts/ │ │ ├── components/ │ │ ├── server/ │ │ │ ├── actions.ts │ │ │ ├── queries.ts │ │ │ └── repository.ts │ │ ├── schemas/ │ │ └── types.ts │ │ │ └── shared/ │ ├── ui/ │ ├── hooks/ │ ├── lib/ │ ├── server/ │ │ ├── db.ts │ │ ├── auth.ts │ │ └── env.ts │ ├── types/ │ └── constants/ │ ├── public/ ├── package.json ├── tsconfig.json └── next.config.ts ```
## 要点
1. app目录结构即是路由 2. page.tsx是入口文件 3. root layout需返回html和body 4. layout:next会根据url自动递归查询并组装layout,如:/users 会自动组装root layout和users的layout 5. template:next会与layout类似,next也会根据url自动递归查询并组装template,区别在于两点:template 会触发状态重置;template位于layout的下一层级 6. server component 和 client component:不需要浏览器环境的UI使用server component,需要浏览器环境的使用client component; 并不是对前端代码或后端代码的划分 7. client component会形成依赖关系,父组件为client component,那么父组件所依赖的子组件也会是client component;因此推荐的架构是:父组件 server component,子组件 client component 8. 通过 props 可绕开client component的依赖链 9. server component 和 client component都是在server完成html渲染:**Client Component** 同样会在服务端执行一次渲染,生成初始的 HTML。这意味着客户端组件的内容在首屏就能可见,且有利于 SEO。随后,这些组件的 JS 代码会被下载、执行,完成 **水合(Hydration)**,使交互逻辑生效。 10. next 数据缓存策略,通过配置fetch接口请求的缓存策略,实现动态渲染(SSR)、过时重新渲染(ISR)、长期缓存(SSG) 11. 前后端接口划分:server component用于查询数据并生成页面;server action则当用户操作触发服务器业务时使用;route handler则主要用于第三方服务 12. redirect 实现跳转 13. revalidatePath 与 revalidateTag 刷新缓存,path刷新路径缓存,tag刷新接口缓存 14. middleware 关卡,拦截路由,判断是否有访问权限,如:未登录无法访问,并跳转到登录页面
## 辨析
1. next通过app router自动构建路由,但并不是每一个文件都会生成路由,只有page.tsx 等约定文件会形成可访问入口