自用- nextjs 001

0 阅读10分钟

一、从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 的两套运行模式

  1. SSR / SSG / ISR 都属于 React 的“服务器端执行”模式

    1. 浏览器请求HTML (到Node服务 -实时执行 React -生成完整HTML) -> HTML(完整) -> 直接渲染 -> JS Hydration

      1. 浏览器请求HTML具体是可理解为请求到Node 服务 执行这个server-side bundle,例如confirm-payment页面 - 打包后的.next/standalone/apps/web-main/.next/server/app/confirm-payment/page.js
  2. CSR 属于 React 的“浏览器端执行”模式

    1. 浏览器请求HTML (CDN/静态资源服务器)-> 空壳index.html-> 下载JS -> 执行JS -> 请求数据 -> 渲染DOM

三、ISR (增量静态生成)

本质上是 React 的服务端渲染模式,增加了缓存 + 定期更新

  1. 初次生成 HTML- 构建时生成 (SSG)

  2. 过期之后,后台的node服务再次生成HTLML (SSR一样)

四、理解Server/client componet 和 SSR/ssg/ISR/csr

SSR/ssg/ISR/csr 【渲染策略】区别的维度是 HTML 在什么时候生成 运行时/构建时/构建+运行/浏览器加载js

Server/client componet 【组件模型】 是组件的代码放在哪里执行, 是否需要 hydration/下发js

  1. Client component 【必须需要在浏览器运行就得使用 例如依赖 DOM / window】

    1. useState/useReducer 等需要组件状态
    2. useEffect、useLayoutEffetc 需要副作用
    3. onClick/onChange 需要交互事件,事件监听只能房子啊浏览器绑定
    4. 浏览器的API window/document/localStorage 等
    5. 第三方的ui组件

五、Hydration

React 在浏览器接管服务器生成的 HTML,把它变成交互式的 React 应用

当前端的 SSR SSG ISR 使用了 client component 此时必然会需要 React Hydrate

  1. 服务端返回html字符串包括 DOM标签、文本内容、React生成的标记(用于Fiber 对应标记) 、 <script客户端 JS bundle

    1.  <div id="root">
         <!-- __react_server_component_marker__ -->
         <button>点击 0</button>
       </div>
       <script src="client-bundle.js"></script>
      
  2. 浏览器执行这个script - client-bundle 包含Client Component 和 水合的相关代码, 水合阶段开始,

    1. 重建 Fiber 树 (组件树 hook\props\事件信息onclick onchange )

    2. 遍历HTML dom

    3. 根据 Fiber 树映射 HTML

      1. 找到 <button> 对应组件 Fiber
      2. 绑定点击事件
      3. 初始化hook等
  3. 【Hydration 依赖 RSC】? 补齐服务器组件树信息

    1. RSC 不在 HTML 里 RSC 也不在客户端 JS bundle 里 RSC 是通过“单独的 Flight 请求(.txt?_rsc)”下发的
  4. nexj开发过程中经常遇到的报错 【React 在 hydrate 阶段发现服务器 HTML 和客户端渲染结果不一致】

// 待完成

六、如何理解nextjs - output 配置和 SSR / SSG / ISR/CSR

  1. output: '默认' | 'standalone' | 'export'; 他指的是一种部署形态/运行环境约束

    1. 默认 | standalone: 包含 Node 运行时服务器,支持运行时的逻辑

      1. 所以此种模式 可支持 SSR / SSG / ISR/CSR
    2. Export 只生成静态文件,没有 node 运行时

      1. 所以这种模式只能支持 CSR/SSG 的渲染模式
  2. 所以说SSR / SSG / ISR/CSR 与 output 配置没有必然关联。所以准确的说法 不是说这个项目是ssr/ssg,而是每个路由/页面自己决定的渲染策略。 例如一个项目我们可以

    1.  /
       ├─ /               → SSG(官网首页)
       ├─ /blog/[slug]    → ISR(博客详情)
       ├─ /search         → SSR(搜索页)
       ├─ /dashboard      → SSR + CSR(强交互)
       └─ /about          → SSG
      
  3. 页面渲染模式决策树

    1.  页面
         ↓
       是否依赖请求上下文?
         ├─ 是 → SSR
         └─ 否
             ↓
       是否配置 revalidate?
         ├─ 是 → ISR
         └─ 否 → SSG
      

七、Nextjs App Router 下 SSR / SSG / ISR 的判断规则是什么

  1. SSR 的判定

    1. 核心原则 只要页面依赖 ‘请求上下文’ 或者明确动态 ,以下属性方法

      1. Dynamic ='force-dynamic' , 明确的要求动态,强制SSR
      2.  // app/page.tsx
         export const dynamic = 'force-dynamic'
        
         export default function Page() {
           return <div>Now: {Date.now()}</div>
         }
        
      3. coookies(), headers()
      4.  // 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>
         }
        
      5. fetch({cache: 'no-store'})
      6.  // 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>
         }
        
      7. 动态 sarchParams
      8.  // app/search/page.tsx
         export default function Page({
           searchParams,
         }: {
           searchParams: { q?: string }
         }) {
           return <div>Search: {searchParams.q}</div>
         }
        
    2. 关于 网络请求 和 ssr/ssg

      1. Nextjs 对 fetch 做了一定扩展,官方推荐在 server Componet 中 推荐只使用fetch, 不要使用axios;nextjs 无法静态分析axios的缓存语义,axios会被直接判定为动态请求SSR;
      2. fetch 配置结果
        默认 / force-cacheSSG
        next: { revalidate }ISR
        cache: 'no-store'ssr
      3. 还是看网络请求在哪个阶段执行,如果是在构建阶段能判定执行例如fetch ,那么这个html 就是 SSG模式;如果是运行时在html生成阶段就执行 请求,html包含请求结果 那就是ssr;当Client Component 一律浏览器执行 判定为csr;
      4.     在client componetn 中 首屏的HTML 可以是 SSG/ssr/isr, 状态事件等副作用一定是来自csr
      5. 组件类型请求库执行环境什么时候执行HTML 渲染模式
        Client Componentfetch浏览器页面 hydrate 后CSR
        Client Componentaxios浏览器页面 hydrate 后CSR
        Server Componentfetch (force-cache)Node / Edge构建阶段SSG
        Server Componentfetch (no-store)Node / Edge请求阶段SSR
        Server Componentfetch (revalidate=N)Node / Edge构建阶段 + 后台刷新ISR
        Server ComponentaxiosNode / Edge构建/请求阶段SSR(无法静态分析)
  2. ISR 的判定,页面判断ISR 必须同时满足:

    1. 页面必须可以静态生成,正好跟ssr相对立,例如以下ssr 强制动态属性方法出现,直接就不是ISR;ISR本质还是静态的 html 如果依赖请求上下文就不成立了

      1. cookies()、headers()、serarchParams、fetch({cache: 'no-store})、export const dynamic='force-dynamic'
    2. 存在可识别的 revalidate 规则, 这个是 isr 唯一标识,如下:

      1. Fetch 级别的
      2.  ## 60 秒后过期,下一次请求出发后台重新生成
         await fetch(url, {
           next: { revalidate: 60 }
         })
        
      3. 页面级别的(Approuter),
      4.  ## 页面中所有fetch 默认 revalidate = 60
         export const revalidate = 60
        
      5.     路由级(Pages Router)
      6.  export async function getStaticProps() {
           return {
             props: {},
             revalidate: 60
           }
         }
        
      7. 特殊的 export const revalidate = 0 等价于 fetch({ cache: 'no-store' }) -> ssr
    3. 使用nexjs fetch 而不是其他例如 axios

      1. Nexjs 只对fetch 做缓存/静态分析/过期管理

八、SSR / SSG / ISR 和 CSR 是接力关系

例如2个页面pageA pageB,

  1. 用户访问pageA: 【ssr模式】

    1. 浏览器Get pageA
    2. 到node 服务,执行react组件 reactDomServer.renderToString
    3. 返回HTml -
    4. 水合 下载jsBundle - React 在浏览器建立 Filber 树,展示页面A
  2. 比如用户在pageA点击按钮 【csr阶段】

    1.  const handleClick = async () => {
         await fetch('/api/submit', { method: 'POST' })
       }
      
    2. 浏览器执行js
    3. React Fiber 更新页面
  3. PageA 点击跳转 pageB

    1. 通过nextjs next/link 或者 router.push 进行客户端路由跳转

      1. React 请求 pageB 的 JSON 数据 + JS bundle

        1. nexjs捕获时间,阻止浏览器默认跳转
        2. 发起请求pageB的 pageData json
        3. 浏览器解析jsboundle 渲染页面B
        4. URL更新 - 页面切换完成
      2. 浏览器hydrate/render pageB

      3. 不经过服务器返回,而是csr + prefetch json ->react渲染

    2. 直接访问刷新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 RouterApp Router
组件类型默认都是 Client Component,没有 Server Component默认 Server Component,可显式使用 Client Component
水合范围整棵页面组件树只 hydrate Client Component
数据获取getServerSideProps / getStaticProps / APIServer Component 可直接 fetch 数据,Client Component 处理局部交互
页面跳转整页刷新或局部 SPASPA 式按需加载 _rsc 片段
JS 体积整页 JS bundle仅 Client Component JS
  1. 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 已经包含在其中。

  1. rsC 主要包含如下:

    1. Server/client Component 的划分规则
    2. React Flight 协议(组件树序列化格式)
    3. 服务器 到 浏览器的 传输机制
    4. Hydrate 时候如何重建组件树
  2. 对照理解

    1.  1、传统 CSR
       JS bundle
         ↓
       React.createRoot(...)
         ↓
       浏览器构建组件树
      
       2、有 RSC(App Router)
       服务器构建组件树
         ↓
       RSC Flight 数据
         ↓
       浏览器重建组件树(hydrate)
      
  3. RSC 为了解决什么问题

    1. Pages Router ssr/ssg 返回的是html,客户端 hydrate 后 页面跳转要么整页刷新,要么csr重新fetch
    2. 问题:页面跳转成本高、Server Componet 无法按需拉,数据UI耦合
  4. 如果一个页面的 page.tsx'use client',并且整棵页面组件树都是 Client Component,那么这个页面在“运行形态上”基本没有使用到 RSC 架构。

    1. RSC 架构解决的是 React 组件树由服务器主导计算, 客户端只负责交互

    2. 当页面 page.txx 是 use client

      1. 整棵组件树是 client componets Tree, react组件逻辑、render决策全部发生在浏览器

      2. Nextjs 仍然会 node服务生成Html - 返回给客户端并且下发js。

        1. 但是这个html不是 server Component 计算的结果” 更像是 “Client App 的 SSR 快照”
      3. RSC Flight 数据基本没有,

        1. 不需要传 Server component 的执行结果
        2. 不需要 .txt?_rsc
        3. Hydrate 只靠 JS bundle + HTML
        4. 和传统 SSR 几乎一致