跳到主要内容
预计阅读 43 分钟

浏览器篇 | SSR流式渲染

前言

你做了一个 React 单页应用(SPA),功能完美,交互流畅。但上线后发现两个问题:一是首屏白屏时间太长——用户打开页面,盯着空白看了好几秒才看到内容;二是 SEO 效果很差——搜索引擎爬虫抓到的 HTML 里只有一个空的 <div id="root"></div>

这两个问题的根源都在于:SPA 是客户端渲染(CSR)——HTML 只是一个空壳,所有内容都靠浏览器执行 JavaScript 后才能渲染出来。

于是你开始研究 SSR(服务端渲染)。进一步了解后发现,SSR 也在不断进化——从传统的”整页渲染完再发送”,到 React 18 引入的流式渲染(Streaming SSR)选择性注水(Selective Hydration),再到 Next.js、Remix 等框架把这些能力封装成开箱即用的方案。

面试中,SSR 相关的问题已经越来越常见,尤其是在中高级岗位。面试官可能会问:

  • CSR、SSR、SSG 有什么区别?各自适合什么场景?
  • 传统 SSR 有什么问题?流式渲染解决了什么?
  • 什么是”注水”(Hydration)?选择性注水又是什么?
  • Next.js 的 App Router 和 Pages Router 在 SSR 上有什么区别?

本章我们就从渲染模式的演进出发,逐一拆解这些概念。


诊断自测

Q1:CSR、SSR、SSG 分别是什么?用一句话概括各自的特点。

点击查看答案
  • CSR(Client-Side Rendering):HTML 是空壳,内容在浏览器端通过 JS 渲染。首屏慢、SEO 差,但交互体验好。
  • SSR(Server-Side Rendering):服务器在请求时生成完整的 HTML 返回。首屏快、SEO 友好,但服务器压力大。
  • SSG(Static Site Generation):在构建时预先生成静态 HTML。速度最快、可 CDN 缓存,但不适合频繁变化的数据。

Q2:什么是”注水”(Hydration)?为什么 SSR 页面需要注水?

点击查看答案

注水(Hydration)是指浏览器收到服务端渲染的 HTML 后,React(或其他框架)在客户端重新执行一遍组件逻辑,把事件监听器绑定到已有的 DOM 上,让静态 HTML 变成可交互的应用。SSR 返回的 HTML 只是”看起来有内容”,但没有任何交互能力(按钮点不动、表单提交不了)。注水就是给这些”静态骨架”注入”活力”的过程。

Q3:传统 SSR 的主要瓶颈是什么?流式渲染如何解决这个问题?

点击查看答案

传统 SSR 的瓶颈是全部或者无(All-or-Nothing):服务器必须等整个页面的数据都准备好、所有组件都渲染完毕后,才能发送 HTML 给浏览器。如果某个组件依赖一个慢接口(比如 3 秒才返回),整个页面都要等 3 秒。流式渲染打破了这个限制——先把已经准备好的部分发送给浏览器(用户可以先看到),慢的部分用 <Suspense> 包裹,等数据就绪后再”流式”追加到页面中。


一、CSR vs SSR vs SSG:三种渲染模式对比

1.1 CSR(Client-Side Rendering)

传统 SPA 使用的渲染方式。服务器只返回一个几乎空白的 HTML:

<!DOCTYPE html>
<html>
<body>
  <div id="root"></div>
  <script src="/bundle.js"></script>
</body>
</html>

浏览器下载并执行 bundle.js,JS 代码发起数据请求、构建 DOM、渲染页面。

整个流程:

1. 浏览器请求 HTML          → 收到空壳
2. 下载 JS bundle           → 等待...
3. 执行 JS                  → 等待...
4. JS 发起 API 请求          → 等待...
5. 拿到数据、渲染 DOM        → 用户终于看到内容

用户在第 5 步之前看到的都是白屏(或 loading)。

优势:

  • 开发简单,前后端完全分离
  • 页面切换体验好(无刷新)
  • 服务器压力小(只返回静态文件)

劣势:

  • 首屏白屏时间长
  • SEO 差(爬虫看到的是空 HTML)
  • JS bundle 大时影响体验

1.2 SSR(Server-Side Rendering)

服务器在收到请求时执行 React 组件,生成完整的 HTML 返回:

<!DOCTYPE html>
<html>
<body>
  <div id="root">
    <h1>Hello, Alice!</h1>
    <p>Welcome to the dashboard.</p>
    <!-- 完整的页面内容 -->
  </div>
  <script src="/bundle.js"></script>
</body>
</html>

整个流程:

1. 浏览器请求 HTML          → 服务器执行组件、获取数据、生成 HTML
2. 收到完整 HTML            → 用户看到内容(FCP 快)
3. 下载 JS bundle           → 等待...
4. Hydration(注水)         → 页面变得可交互(TTI)

用户在第 2 步就能看到内容,但要等到第 4 步才能交互。

优势:

  • 首屏快(FCP 早)
  • SEO 友好
  • 适合内容密集型页面

劣势:

  • 服务器压力大(每个请求都要渲染)
  • TTFB(Time To First Byte)可能慢(服务器需要时间渲染)
  • 注水前页面不可交互,可能造成”可看不可用”的尴尬

1.3 SSG(Static Site Generation)

构建时(build time)就生成好所有页面的静态 HTML。部署时直接放 CDN 即可。

优势:

  • 速度最快(CDN 直出,无需服务器渲染)
  • SEO 极好
  • 可靠性高(纯静态,不怕服务器挂)

劣势:

  • 数据变化后需要重新构建
  • 不适合高度个性化的页面(每个用户看到不同内容)
  • 页面数量极大时,构建时间可能很长

1.4 选择策略

场景推荐方案原因
博客、文档、营销页SSG内容变化少,CDN 直出最快
电商商品页、新闻详情SSR(或 ISR)SEO 重要,数据较实时
后台管理系统CSR不需要 SEO,交互复杂
仪表盘 + 公开首页混合模式首页 SSR/SSG + 后台 CSR

现代框架(如 Next.js)允许你在同一个项目中混合使用多种渲染模式——某些页面 SSG,某些页面 SSR,某些组件 CSR。


二、SSR 的优势与劣势深入

2.1 SSR 的核心优势

更快的首屏内容展示(FCP): 用户不需要等 JS 下载和执行就能看到完整的页面内容。对于网络较慢的设备(低端手机、弱网环境),这个差异尤其明显。

更好的 SEO: 搜索引擎爬虫(尤其是非 Google 的爬虫)不一定会执行 JavaScript。SSR 返回的是完整的 HTML,爬虫可以直接解析内容。

更好的社交媒体分享: 微信、Twitter、Facebook 等平台在抓取链接预览时,通常只读取 HTML 中的 meta 标签。SSR 可以根据页面内容动态设置 <meta> 标签。

2.2 传统 SSR 的瓶颈

传统 SSR 有一个严重的问题——一切都是”全部或者无”的:

  1. 数据获取:必须在服务端获取所有数据后才能开始渲染
  2. HTML 生成:必须渲染完整的 HTML 后才能发送给浏览器
  3. JS 加载:必须下载所有 JS 代码后才能开始注水
  4. 注水:必须对整个页面完成注水后才能交互
传统 SSR 的瀑布流:

服务端:[等数据A 3s][等数据B 1s][渲染HTML 0.5s]
                                              │ 发送 HTML
浏览器:                                       [下载JS 1s][注水 0.5s] → 可交互

                                        用户看到内容
                                       (等了 4.5s!)

如果数据 A 需要 3 秒,整个页面就要等 3 秒才能开始发送——即使数据 B 和其他不依赖数据的部分早就准备好了。


三、流式渲染(Streaming SSR)

3.1 核心思想

流式渲染的核心思想是:不等所有内容都准备好,先把准备好的部分发给浏览器,剩下的后续追加。

流式 SSR:

服务端:[准备好头部+骨架]→ 立即发送
                       [等数据A 3s]→ 追加发送
浏览器:[收到头部+骨架] → 用户看到骨架
        [收到数据A的HTML] → 用户看到完整内容

浏览器在收到第一块 HTML 后就可以开始渲染,用户几乎立刻就能看到页面的”骨架”,等慢数据就绪后,对应区域的内容会自动填充进去。

3.2 React 18 的 renderToPipeableStream

React 18 引入了 renderToPipeableStream API 来支持流式渲染(取代了之前的 renderToString):

// server.js
import { renderToPipeableStream } from 'react-dom/server';
import App from './App';

app.get('/', (req, res) => {
  const { pipe, abort } = renderToPipeableStream(
    <App />,
    {
      bootstrapScripts: ['/bundle.js'],
      onShellReady() {
        // Shell(Suspense 边界以外的内容)准备好了
        // 可以开始流式发送
        res.statusCode = 200;
        res.setHeader('Content-Type', 'text/html');
        pipe(res);
      },
      onShellError(error) {
        // Shell 渲染失败,返回错误页面
        res.statusCode = 500;
        res.send('<!DOCTYPE html><html><body><p>Something went wrong</p></body></html>');
      },
      onAllReady() {
        // 所有内容(包括 Suspense 内的)都准备好了
        // 如果你想做 SSG(crawlers),可以在这里发送
      },
      onError(error) {
        console.error(error);
      }
    }
  );

  // 超时处理
  setTimeout(() => abort(), 10000);
});

两个关键回调:

  • onShellReady:Suspense 边界以外的内容渲染完毕时触发。这是大多数情况下开始 pipe 的时机——用户先看到页面骨架
  • onAllReady所有内容(包括 Suspense 内的异步部分)都渲染完毕时触发。适用于爬虫场景(爬虫需要完整内容)

3.3 Suspense + Streaming 的协作

流式渲染的能力需要配合 <Suspense> 组件使用:

function App() {
  return (
    <html>
      <body>
        <Header />
        <Suspense fallback={<Spinner />}>
          <MainContent />   {/* 依赖一个慢接口 */}
        </Suspense>
        <Suspense fallback={<SidebarSkeleton />}>
          <Sidebar />       {/* 依赖另一个接口 */}
        </Suspense>
        <Footer />
      </body>
    </html>
  );
}

流式渲染的过程:

  1. 服务端先渲染 <Header /><Footer />(不依赖异步数据)
  2. 遇到 <Suspense> 边界,先输出 fallback(<Spinner /><SidebarSkeleton />
  3. onShellReady 触发,开始发送 HTML 给浏览器
  4. 浏览器收到 HTML 后立即渲染——用户看到头部、底部和两个 loading 状态
  5. Sidebar 的数据先返回 → 服务端渲染 <Sidebar />,通过流追加一段 <script> 标签,让浏览器用真实内容替换 <SidebarSkeleton />
  6. MainContent 的数据后返回 → 同理替换 <Spinner />

关键机制: 服务端追加的内容不是直接替换 DOM,而是通过内联的 <script> 标签,调用 React 的运行时代码来执行替换。这个过程对用户来说是无缝的。


四、选择性注水(Selective Hydration)

4.1 传统注水的问题

在 React 18 之前,注水是同步且一次性的。浏览器必须下载完所有 JS 代码,然后一口气把整个页面注水完毕后,页面才能交互。如果页面很大,注水过程可能需要好几秒——在这期间用户点什么都没反应。

4.2 选择性注水解决了什么

React 18 引入了选择性注水(Selective Hydration),带来两个关键改进:

1. 不需要等所有 JS 加载完才开始注水

配合 React.lazy<Suspense>,不同组件的代码可以独立加载。某个组件的代码加载完了,React 就可以先注水它,不需要等其他组件。

const Comments = React.lazy(() => import('./Comments'));
const Sidebar = React.lazy(() => import('./Sidebar'));

function App() {
  return (
    <>
      <Header />
      <Suspense fallback={<Spinner />}>
        <Comments />
      </Suspense>
      <Suspense fallback={<SidebarSkeleton />}>
        <Sidebar />
      </Suspense>
    </>
  );
}

如果 Sidebar 的代码先加载完,React 会先对 Sidebar 进行注水,不等 Comments

2. 用户交互可以打断注水顺序

如果 React 正在注水 Sidebar,但用户点击了 Comments 区域——React 会暂停 Sidebar 的注水,优先注水 Comments,让用户的交互尽快得到响应。这就是”选择性”的含义——根据用户的行为动态调整注水的优先级。

传统注水:
[====== 注水整个页面 ======] → 才能交互

选择性注水:
[Header 注水][Comments 注水][Sidebar 注水] → 各自独立

            用户点击了这里?优先注水这里!

五、现代框架的 SSR 方案对比

5.1 Next.js

Next.js 是 React 生态中最主流的 SSR 框架。它经历了从 Pages RouterApp Router 的重大演进。

Pages Router(传统方式):

// pages/posts/[id].js
export async function getServerSideProps({ params }) {
  const post = await fetchPost(params.id);
  return { props: { post } };
}

export default function Post({ post }) {
  return <article>{post.content}</article>;
}
  • getServerSideProps:每次请求时在服务端执行,获取数据后传给组件
  • getStaticProps + getStaticPaths:构建时生成静态页面(SSG)
  • 数据获取和渲染在页面级别,不能细粒度控制

App Router(React Server Components):

// app/posts/[id]/page.js
async function Post({ params }) {
  const post = await fetchPost(params.id); // 直接在组件中获取数据

  return (
    <article>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
      <Suspense fallback={<Spinner />}>
        <Comments postId={params.id} />
      </Suspense>
    </article>
  );
}

export default Post;
  • Server Components:默认在服务端渲染,不发送到客户端,减少 JS bundle 大小
  • Client Components:用 'use client' 标记的组件在客户端渲染,支持交互
  • 天然支持流式渲染和 Suspense
  • 数据获取在组件级别,粒度更细

5.2 Remix

Remix 的哲学和 Next.js 有所不同——它更贴近 Web 标准(HTML form、HTTP caching 等)。

// app/routes/posts.$id.tsx
export async function loader({ params }) {
  const post = await fetchPost(params.id);
  return json({ post });
}

export default function Post() {
  const { post } = useLoaderData();

  return (
    <article>
      <h1>{post.title}</h1>
      <Await resolve={post.comments} fallback={<Spinner />}>
        {(comments) => <CommentList comments={comments} />}
      </Await>
    </article>
  );
}
  • Loader:在服务端获取数据(类似 Next.js 的 getServerSideProps)
  • defer + <Await>:支持流式渲染,可以先返回部分数据,慢数据后续流式补充
  • 嵌套路由:父路由和子路由可以并行获取数据,而不是瀑布式
  • 渐进增强:即使 JS 加载失败,表单提交等核心功能仍然可用

5.3 对比总结

特性Next.js (App Router)Remix
服务端组件React Server ComponentsLoader + 传统组件
流式渲染Suspense + 自动流式defer + Await
数据获取组件内直接 async/awaitloader 函数
代码分割自动 + Server/Client 分离自动(基于路由)
渲染模式SSR / SSG / ISR / CSR 灵活切换主要 SSR(也支持 SSG)
设计哲学React 最新特性先行拥抱 Web 标准

六、一些实际的选型建议

选择渲染方案时,不要盲目追求”最新最先进”,而要根据实际情况决定:

什么时候不需要 SSR?

  • 纯内部工具/后台管理系统
  • 不需要 SEO 的应用
  • 用户都在高速网络和现代设备上

什么时候需要 SSR?

  • 面向公众的内容型网站(SEO 重要)
  • 首屏性能是核心指标
  • 需要支持社交媒体分享预览

什么时候需要流式渲染?

  • 页面由多个数据源组成,部分数据获取较慢
  • 希望用户尽早看到页面骨架
  • 已经在使用 React 18+ 或支持流式的框架

常见误区

误区一:“SSR 就是把 React 组件在服务端跑一遍”

技术上没错,但这只是 SSR 最基础的部分。完整的 SSR 还包括数据预获取、HTML 流式发送、客户端注水、状态同步等一系列复杂的环节。光把组件在服务端渲染成 HTML 字符串不难,难的是让这个 HTML 在到达浏览器后能无缝”复活”成可交互的应用。

误区二:“SSR 页面一定比 CSR 快”

不一定。SSR 的首屏内容展示(FCP)通常更快,但 TTFB(首字节时间)可能更慢——因为服务器需要时间获取数据和渲染 HTML。如果服务端的数据获取很慢(比如依赖多个外部 API),SSR 的 TTFB 可能比 CSR 还差。流式渲染就是为了解决这个问题——先发快的部分,慢的后续补上。

误区三:“注水就是重新渲染一遍”

注水(Hydration)不是重新渲染。React 在注水时不会重新创建 DOM——它会复用服务端已经生成的 DOM 节点,只是在这些节点上附加事件监听器和 React 的内部状态。如果注水时发现客户端渲染的结果和服务端的 HTML 不一致(hydration mismatch),React 会发出警告,并可能强制重新渲染——这会导致性能下降。

误区四:“用了 Server Components 就不需要客户端 JS 了”

Server Components 确实不会把自身的代码发送到客户端,但它们只能处理不需要交互的部分。任何需要用户交互的功能(点击按钮、输入表单、状态管理)仍然需要 Client Components,这些组件的 JS 代码仍然会被发送到浏览器。Server Components 减少的是那些纯展示、不需要交互的组件的 JS 开销。


小结

本章我们从渲染模式的对比出发,深入探讨了 SSR 的演进——从传统的全量渲染到流式渲染和选择性注水。

核心要点

  1. CSR 首屏慢、SEO 差但开发简单;SSR 首屏快、SEO 好但服务器压力大;SSG 最快但不适合动态内容
  2. 传统 SSR 的瓶颈是”全部或者无”——必须等所有数据就绪才能发送 HTML
  3. 流式渲染打破了这个限制:先发准备好的部分,慢数据后续追加
  4. React 18 的 renderToPipeableStream + <Suspense> 是流式 SSR 的核心 API
  5. 选择性注水允许不同组件独立注水,用户交互可以打断注水顺序
  6. Next.js App Router 引入了 Server Components,天然支持流式渲染
  7. Remix 通过 defer + <Await> 实现流式渲染,哲学更贴近 Web 标准
  8. 选型不要盲目——根据 SEO 需求、首屏性能要求、数据实时性来决定

本章思维导图

SSR 与流式渲染
  • 三种渲染模式
    • CSR:浏览器渲染,首屏慢,SEO 差
    • SSR:服务端渲染,首屏快,SEO 好
    • SSG:构建时生成,最快,适合静态内容
  • 传统 SSR 的问题
    • 数据获取:等所有数据就绪
    • HTML 生成:等整页渲染完毕
    • 注水:等所有 JS 加载 + 整页注水
    • "全部或者无"的瀑布流
  • 流式渲染(Streaming SSR)
    • 核心思想:先发快的,慢的后续追加
    • React 18: renderToPipeableStream
    • onShellReady vs onAllReady
    • Suspense 标记异步边界 + fallback
  • 选择性注水(Selective Hydration)
    • 不等所有 JS 加载完才注水
    • 用户交互打断注水顺序
    • React.lazy + Suspense 配合
  • 现代框架方案
    • Next.js
      • Pages Router: getServerSideProps / getStaticProps
      • App Router: Server Components + Suspense
    • Remix
      • loader + defer + Await
      • 嵌套路由并行数据获取
      • 渐进增强
  • 选型建议
    • 不需要 SEO → CSR
    • 内容型网站 → SSR / SSG
    • 多数据源慢接口 → 流式渲染

练习挑战

第一题 ⭐(基础):选择渲染模式

为以下场景选择最合适的渲染模式(CSR / SSR / SSG),并说明理由。

  1. 企业官网(内容半年更新一次)
  2. 电商商品详情页(SEO 重要,价格和库存实时变化)
  3. 公司内部的项目管理工具
  4. 个人技术博客
点击查看答案
  1. 企业官网 → SSG。内容变化极少,构建时生成静态 HTML,通过 CDN 分发,速度最快、SEO 最好、运维成本最低。
  2. 电商商品详情页 → SSR(或 ISR/混合方案)。SEO 重要,需要搜索引擎收录;商品基本信息可以 SSR/SSG,价格和库存等实时数据可以在客户端动态更新。Next.js 的 ISR(Incremental Static Regeneration)是个很好的选择——大部分时间用缓存的静态页面,定期后台更新。
  3. 内部项目管理工具 → CSR。不需要 SEO(内部工具不需要被搜索引擎收录),交互复杂(看板拖拽、实时协作等),CSR 开发最简单且交互体验最好。
  4. 个人技术博客 → SSG。文章发布后内容不变,SSG 生成静态页面最合适。用 Astro、Next.js 的静态导出或 Hugo 等工具都行。

第二题 ⭐⭐(进阶):分析流式渲染的好处

下面是一个 Next.js App Router 的页面组件。假设 fetchPost 需要 200ms,fetchComments 需要 3 秒。请分析:传统 SSR 和流式 SSR 下,用户分别在什么时间点看到内容?

// app/posts/[id]/page.js
import { Suspense } from 'react';

async function PostContent({ id }) {
  const post = await fetchPost(id);
  return <article>{post.content}</article>;
}

async function Comments({ postId }) {
  const comments = await fetchComments(postId);
  return (
    <ul>
      {comments.map(c => <li key={c.id}>{c.text}</li>)}
    </ul>
  );
}

export default function PostPage({ params }) {
  return (
    <div>
      <Header />
      <Suspense fallback={<p>Loading post...</p>}>
        <PostContent id={params.id} />
      </Suspense>
      <Suspense fallback={<p>Loading comments...</p>}>
        <Comments postId={params.id} />
      </Suspense>
      <Footer />
    </div>
  );
}
点击查看答案

传统 SSR:

  • 服务端等待 fetchPost(200ms)和 fetchComments(3s)都完成后,才开始发送 HTML
  • 用户在 ~3s 后一次性看到完整页面(文章 + 评论)
  • 在这 3 秒内,用户看到的是白屏

流式 SSR:

  • 服务端先渲染 <Header /><Footer /> 和两个 Suspense 的 fallback
  • ~0msonShellReady 触发,开始发送 HTML。用户几乎立刻看到页面骨架(Header + “Loading post…” + “Loading comments…” + Footer)
  • ~200msfetchPost 完成,服务端流式发送 <PostContent /> 的 HTML,替换 “Loading post…”。用户看到文章内容
  • ~3sfetchComments 完成,服务端流式发送 <Comments /> 的 HTML,替换 “Loading comments…”。用户看到评论

流式渲染的关键好处:用户在 200ms 后就能看到文章主体内容,不需要等 3 秒的评论接口。页面是渐进式加载的,用户体验好得多。

第三题 ⭐⭐⭐(综合):解释 Hydration Mismatch

以下代码在 SSR 时会产生 hydration mismatch 警告。找出原因并给出修复方案。

function Greeting() {
  const hour = new Date().getHours();
  const greeting = hour < 12 ? 'Good morning' : 'Good afternoon';

  return <h1>{greeting}, {Math.random().toFixed(4)}</h1>;
}
点击查看答案

问题原因:

Hydration mismatch 发生在服务端渲染的 HTML 和客户端注水时渲染的结果不一致时。这段代码有两个问题:

  1. new Date().getHours():服务端和客户端的时间可能不同(时区差异、网络传输延迟),导致 greeting 不一致
  2. Math.random():每次调用结果不同,服务端和客户端几乎不可能生成相同的随机数

修复方案:

'use client';

import { useState, useEffect } from 'react';

function Greeting() {
  const [greeting, setGreeting] = useState('Hello');
  const [randomNum, setRandomNum] = useState('');

  useEffect(() => {
    // useEffect 只在客户端执行,不会影响服务端渲染
    const hour = new Date().getHours();
    setGreeting(hour < 12 ? 'Good morning' : 'Good afternoon');
    setRandomNum(Math.random().toFixed(4));
  }, []);

  return <h1>{greeting}, {randomNum}</h1>;
}

或者使用 suppressHydrationWarning(不推荐,只是隐藏警告):

return <h1 suppressHydrationWarning>{greeting}, {Math.random().toFixed(4)}</h1>;

核心原则: 任何在服务端和客户端可能产生不同结果的逻辑(时间、随机数、浏览器 API、用户信息等),都应该放在 useEffect 中,或者使用 suppressHydrationWarning 明确标注。更好的做法是让这些内容作为 Client Component 渲染,服务端只渲染确定性的内容。


自我检测

  • 能清楚说出 CSR、SSR、SSG 的区别、各自的优缺点和适用场景
  • 能解释”注水(Hydration)“的概念——不是重新渲染,而是在已有 DOM 上附加事件和状态
  • 能描述传统 SSR 的”全部或者无”瓶颈
  • 能解释流式渲染如何解决这个瓶颈——Suspense + fallback + 渐进式发送
  • 能说出 renderToPipeableStreamonShellReadyonAllReady 的区别
  • 能解释选择性注水的两个关键改进:独立加载注水 + 用户交互优先
  • 能对比 Next.js(App Router vs Pages Router)和 Remix 的 SSR 方案
  • 能说出什么是 Hydration Mismatch,以及如何避免
  • 能根据实际场景(SEO 需求、数据实时性、交互复杂度)选择合适的渲染模式

购买课程解锁全部内容

大厂前端面试通关:71 篇构建完整知识体系

¥89.90