一、从HTML生成时机区分
| 渲染策略 | HTML生成位置 | 适用类型 | 浏览器 | |
|---|---|---|---|---|
| CSR (Client-Siderendering) | 浏览器 | 空壳html -> 【浏览器】运行js后生成 | 管理后台,单页应用, 没有 SEO 要求的应用 | |
| SSr (Serve-Side Rendering) | node服务/ | 每次请求时候【node服务】实时生成HTML | 强 SEO, 首屏速度要求较高 | 浏览器 hydrate(client component 需要用到hydrate) |
| SSG(Static Site Generation) | node服务/ | 浏览器请求 - CDN (构建时生成HTML) | 内容很少变动的应用,例如:文档类型,官网展示类型,手册等 | |
| ISR(Incremental Static Regeneration) | node服务/ | 构建时(ssg) + 后台再生成(ssr一样) | 内容会有部分更新的,例如新闻类 |
二、React 的两套运行模式
-
SSR / SSG / ISR 都属于 React 的“服务器端执行”模式
-
浏览器请求HTML (到Node服务 -实时执行 React -生成完整HTML) -> HTML(完整) -> 直接渲染 -> JS Hydration
- 浏览器请求HTML具体是可理解为请求到Node 服务 执行这个server-side bundle,例如confirm-payment页面 - 打包后的.next/standalone/apps/web-main/.next/server/app/confirm-payment/page.js
-
-
CSR 属于 React 的“浏览器端执行”模式
- 浏览器请求HTML (CDN/静态资源服务器)-> 空壳index.html-> 下载JS -> 执行JS -> 请求数据 -> 渲染DOM
三、ISR (增量静态生成)
本质上是 React 的服务端渲染模式,增加了缓存 + 定期更新
-
初次生成 HTML- 构建时生成 (SSG)
-
过期之后,后台的node服务再次生成HTLML (SSR一样)
四、理解Server/client componet 和 SSR/ssg/ISR/csr
SSR/ssg/ISR/csr 【渲染策略】区别的维度是 HTML 在什么时候生成 运行时/构建时/构建+运行/浏览器加载js
Server/client componet 【组件模型】 是组件的代码放在哪里执行, 是否需要 hydration/下发js
-
Client component 【必须需要在浏览器运行就得使用 例如依赖 DOM / window】
- useState/useReducer 等需要组件状态
- useEffect、useLayoutEffetc 需要副作用
- onClick/onChange 需要交互事件,事件监听只能房子啊浏览器绑定
- 浏览器的API window/document/localStorage 等
- 第三方的ui组件
五、Hydration
React 在浏览器接管服务器生成的 HTML,把它变成交互式的 React 应用
当前端的 SSR SSG ISR 使用了 client component 此时必然会需要 React Hydrate
-
服务端返回html字符串包括 DOM标签、文本内容、React生成的标记(用于Fiber 对应标记) 、
<script客户端 JS bundle-
<div id="root"> <!-- __react_server_component_marker__ --> <button>点击 0</button> </div> <script src="client-bundle.js"></script>
-
-
浏览器执行这个script - client-bundle 包含Client Component 和 水合的相关代码, 水合阶段开始,
-
重建 Fiber 树 (组件树 hook\props\事件信息onclick onchange )
-
遍历HTML dom
-
根据 Fiber 树映射 HTML
- 找到
<button>对应组件 Fiber - 绑定点击事件
- 初始化hook等
- 找到
-
-
【Hydration 依赖 RSC】? 补齐服务器组件树信息
- RSC 不在 HTML 里 RSC 也不在客户端 JS bundle 里 RSC 是通过“单独的 Flight 请求(.txt?_rsc)”下发的
-
nexj开发过程中经常遇到的报错 【React 在 hydrate 阶段发现服务器 HTML 和客户端渲染结果不一致】
// 待完成
六、如何理解nextjs - output 配置和 SSR / SSG / ISR/CSR
-
output: '默认' | 'standalone' | 'export'; 他指的是一种部署形态/运行环境约束
-
默认 | standalone: 包含 Node 运行时服务器,支持运行时的逻辑
- 所以此种模式 可支持 SSR / SSG / ISR/CSR
-
Export 只生成静态文件,没有 node 运行时
- 所以这种模式只能支持 CSR/SSG 的渲染模式
-
-
所以说SSR / SSG / ISR/CSR 与 output 配置没有必然关联。所以准确的说法 不是说这个项目是ssr/ssg,而是每个路由/页面自己决定的渲染策略。 例如一个项目我们可以
-
/ ├─ / → SSG(官网首页) ├─ /blog/[slug] → ISR(博客详情) ├─ /search → SSR(搜索页) ├─ /dashboard → SSR + CSR(强交互) └─ /about → SSG
-
-
页面渲染模式决策树
-
页面 ↓ 是否依赖请求上下文? ├─ 是 → SSR └─ 否 ↓ 是否配置 revalidate? ├─ 是 → ISR └─ 否 → SSG
-
七、Nextjs App Router 下 SSR / SSG / ISR 的判断规则是什么
-
SSR 的判定
-
核心原则 只要页面依赖 ‘请求上下文’ 或者明确动态 ,以下属性方法
- Dynamic ='force-dynamic' , 明确的要求动态,强制SSR
-
// app/page.tsx export const dynamic = 'force-dynamic' export default function Page() { return <div>Now: {Date.now()}</div> } - coookies(), headers()
-
// app/profile/page.tsx import { cookies,headers } from 'next/headers' export default function Page() { const cookieStore = cookies() const token = cookieStore.get('token') const h = headers() const ua = h.get('user-agent') return <div>Token: {token?.value}</div> } - fetch({cache: 'no-store'})
-
// app/search/page.tsx export default async function Page() { const res = await fetch('https://api.xxx.com/search', { cache: 'no-store', }) const data = await res.json() return <pre>{JSON.stringify(data)}</pre> } - 动态 sarchParams
-
// app/search/page.tsx export default function Page({ searchParams, }: { searchParams: { q?: string } }) { return <div>Search: {searchParams.q}</div> }
-
关于 网络请求 和 ssr/ssg
- Nextjs 对 fetch 做了一定扩展,官方推荐在 server Componet 中 推荐只使用fetch, 不要使用axios;nextjs 无法静态分析axios的缓存语义,axios会被直接判定为动态请求SSR;
-
fetch 配置 结果 默认 / force-cache SSG next: { revalidate } ISR cache: 'no-store' ssr - 还是看网络请求在哪个阶段执行,如果是在构建阶段能判定执行例如fetch ,那么这个html 就是 SSG模式;如果是运行时在html生成阶段就执行 请求,html包含请求结果 那就是ssr;当Client Component 一律浏览器执行 判定为csr;
- 在client componetn 中 首屏的HTML 可以是 SSG/ssr/isr, 状态事件等副作用一定是来自csr
-
组件类型 请求库 执行环境 什么时候执行 HTML 渲染模式 Client Component fetch 浏览器 页面 hydrate 后 CSR Client Component axios 浏览器 页面 hydrate 后 CSR Server Component fetch (force-cache) Node / Edge 构建阶段 SSG Server Component fetch (no-store) Node / Edge 请求阶段 SSR Server Component fetch (revalidate=N) Node / Edge 构建阶段 + 后台刷新 ISR Server Component axios Node / Edge 构建/请求阶段 SSR(无法静态分析)
-
-
ISR 的判定,页面判断ISR 必须同时满足:
-
页面必须可以静态生成,正好跟ssr相对立,例如以下ssr 强制动态属性方法出现,直接就不是ISR;ISR本质还是静态的 html 如果依赖请求上下文就不成立了
- cookies()、headers()、serarchParams、fetch({cache: 'no-store})、export const dynamic='force-dynamic'
-
存在可识别的 revalidate 规则, 这个是 isr 唯一标识,如下:
- Fetch 级别的
-
## 60 秒后过期,下一次请求出发后台重新生成 await fetch(url, { next: { revalidate: 60 } }) - 页面级别的(Approuter),
-
## 页面中所有fetch 默认 revalidate = 60 export const revalidate = 60 - 路由级(Pages Router)
-
export async function getStaticProps() { return { props: {}, revalidate: 60 } } - 特殊的 export const revalidate = 0 等价于 fetch({ cache: 'no-store' }) -> ssr
-
使用nexjs fetch 而不是其他例如 axios
- Nexjs 只对fetch 做缓存/静态分析/过期管理
-
八、SSR / SSG / ISR 和 CSR 是接力关系
例如2个页面pageA pageB,
-
用户访问pageA: 【ssr模式】
- 浏览器Get pageA
- 到node 服务,执行react组件 reactDomServer.renderToString
- 返回HTml -
- 水合 下载jsBundle - React 在浏览器建立 Filber 树,展示页面A
-
比如用户在pageA点击按钮 【csr阶段】
-
const handleClick = async () => { await fetch('/api/submit', { method: 'POST' }) } - 浏览器执行js
- React Fiber 更新页面
-
-
PageA 点击跳转 pageB
-
通过nextjs next/link 或者 router.push 进行客户端路由跳转
-
React 请求 pageB 的 JSON 数据 + JS bundle
- nexjs捕获时间,阻止浏览器默认跳转
- 发起请求pageB的 pageData json
- 浏览器解析jsboundle 渲染页面B
- URL更新 - 页面切换完成
-
浏览器hydrate/render pageB
-
不经过服务器返回,而是csr + prefetch json ->react渲染
-
-
直接访问刷新pageB 【ssr模式】
-
Pages Router vs App Router
RSC (React Server Components)【架构/协议层】
RSC 是一种架构思想,app router 支持RSC,在于 server componet 处理核心的页面,客户端组件处理需要交互的组件 也就是 客户端的js是最小化的;
Server Component = 单个组件技术特性。
RSC 架构 = 页面/应用设计理念:以 Server Component 为主、客户端最小化 JS、Server 直接拉取数据。
App Router + 默认 Server Component 只是提供了条件和能力,是否是 RSC 架构取决于整体设计,不是开关式的自动状态。
RSC 即 React Server Components,它是 React 团队提出的一种新组件类型。核心概念:
- 组件运行在服务器端,而不是在客户端浏览器里。
- 生成的 HTML 可以直接发送给客户端,减少浏览器渲染和 JS 体积。
- 可以直接访问服务器端资源(数据库、文件系统、API),不需要通过客户端再请求接口。
- 可以和传统的 React Client Components 共存。
Rsc 是一整套的 ‘服务器驱动 React UI' 的系统,RSC 架构是指 整个应用或模块采用 Server Component 渲染为主的设计思路。
| 特性 | Pages Router | App Router |
|---|---|---|
| 组件类型 | 默认都是 Client Component,没有 Server Component | 默认 Server Component,可显式使用 Client Component |
| 水合范围 | 整棵页面组件树 | 只 hydrate Client Component |
| 数据获取 | getServerSideProps / getStaticProps / API | Server Component 可直接 fetch 数据,Client Component 处理局部交互 |
| 页面跳转 | 整页刷新或局部 SPA | SPA 式按需加载 _rsc 片段 |
| JS 体积 | 整页 JS bundle | 仅 Client Component JS |
- RSC 请求的是 服务端组件的渲染结果 + 组件数结构 + 数据引流 的序列化流
页面跳转和渲染是通过 RSC 请求机制 实现的 SPA 式更新
App Router 内部机制
- 即使页面都是 Client Component,Next.js App Router 为了 统一路由更新机制,会在 SPA 跳转时通过
_rsc请求来获取页面内容。 - 浏览器会请求
.txt?_rsc,返回 HTML/JS 片段,更新页面而不刷新整页。
导出静态 HTML + RSC payload
next export为了保持 App Router 的 SPA 式路由功能,即便全是 Client Component,也会生成对应.txt?_rsc文件作为内部跳转的目标。- 这样当你点击链接或
window.location.href时,可以用_rsc请求做局部刷新。
内部统一策略
- Next.js App Router 设计为:
- 所有页面(Server Component 或 Client Component)都可能触发
_rsc请求。
-
Server Component 页面真正使用 RSC Payload;
-
Client Component 页面,返回的
.txt?_rsc内容其实只是静态 HTML,Client Component HTML 已经包含在其中。
-
rsC 主要包含如下:
- Server/client Component 的划分规则
- React Flight 协议(组件树序列化格式)
- 服务器 到 浏览器的 传输机制
- Hydrate 时候如何重建组件树
-
对照理解
-
1、传统 CSR JS bundle ↓ React.createRoot(...) ↓ 浏览器构建组件树 2、有 RSC(App Router) 服务器构建组件树 ↓ RSC Flight 数据 ↓ 浏览器重建组件树(hydrate)
-
-
RSC 为了解决什么问题
- Pages Router ssr/ssg 返回的是html,客户端 hydrate 后 页面跳转要么整页刷新,要么csr重新fetch
- 问题:页面跳转成本高、Server Componet 无法按需拉,数据UI耦合
-
如果一个页面的
page.tsx是'use client',并且整棵页面组件树都是 Client Component,那么这个页面在“运行形态上”基本没有使用到 RSC 架构。-
RSC 架构解决的是 React 组件树由服务器主导计算, 客户端只负责交互
-
当页面 page.txx 是 use client
-
整棵组件树是 client componets Tree, react组件逻辑、render决策全部发生在浏览器
-
Nextjs 仍然会 node服务生成Html - 返回给客户端并且下发js。
- 但是这个html不是 server Component 计算的结果” 更像是 “Client App 的 SSR 快照”
-
RSC Flight 数据基本没有,
- 不需要传 Server component 的执行结果
- 不需要 .txt?_rsc
- Hydrate 只靠 JS bundle + HTML
- 和传统 SSR 几乎一致
-
-