3.7 SSR 与 Next.js


文档摘要

3.7 SSR 与 Next.js 本节摘要:服务端渲染(SSR)让服务器预先渲染 HTML,解决客户端渲染(CSR)的首屏慢与 SEO 差两大痛点。本节先讲 SSR 的工作流程与优劣,再上手 Next.js——创建项目、文件路由、getServerSideProps 取数、预渲染模式选择,最后给出「什么项目该用 SSR」的判断。 上手前先明确 阅读完本节,你应当能够: 画出 SSR 的完整工作流程,说明水合的含义 对比 CSR 与 SSR 在首屏、SEO、服务器负载上的差异 用 Next.

3.7 SSR 与 Next.js

本节摘要:服务端渲染(SSR)让服务器预先渲染 HTML,解决客户端渲染(CSR)的首屏慢与 SEO 差两大痛点。本节先讲 SSR 的工作流程与优劣,再上手 Next.js——创建项目、文件路由、getServerSideProps 取数、预渲染模式选择,最后给出「什么项目该用 SSR」的判断。

上手前先明确

阅读完本节,你应当能够:

  1. 画出 SSR 的完整工作流程,说明水合的含义
  2. 对比 CSR 与 SSR 在首屏、SEO、服务器负载上的差异
  3. 用 Next.js 创建项目并理解文件路由
  4. 用 getServerSideProps 在服务器端获取数据
  5. 判断自己的项目该不该用 SSR

一、问题与直觉:CSR 的两个隐痛

CSR(客户端渲染)是纯 SPA 的渲染方式:浏览器先下载一个空 HTML,再下载 JavaScript,JS 执行后渲染出内容。它的两个隐痛:

首屏慢。用户看到内容前,要等 JS 下载 + 执行。网络差时,白屏几秒很正常。搜索引擎和用户都不会耐心等。

SEO 差。搜索引擎爬虫抓取页面时,如果只拿到空 HTML,页面的内容无法被索引。虽然现代爬虫能执行 JS,但延迟索引、不完整渲染的问题依然存在。

SSR 的思路是反着来:服务器先把组件渲染成 HTML,直接发给浏览器。 浏览器拿到就有内容,首屏快;爬虫拿到的 HTML 里有完整内容,SEO 好。

SOURCE 原文对 SSR 的定义很直接:在服务器端将 React 组件渲染成 HTML 的技术。

二、核心原理:SSR 的工作流程

一次 SSR 页面访问的完整流程:

八个步骤里,最值得理解的是「水合」(Hydration):浏览器拿到服务器渲染的 HTML 后,还要下载 JS,然后让 React 接管这个已有的 HTML——把事件监听器、交互状态「附着」到已渲染的 DOM 上。水合之前页面能看不能点,水合之后才完整可用。

为什么要水合

有人问:服务器都渲染好了,为什么还要 JS?答案:HTML 是静态的,交互需要 JavaScript。水合让 React 的「数据驱动视图」接管已经存在的 DOM,而不是重新渲染一遍。这也带来一个要求:服务端渲染的 HTML 必须和客户端首次渲染的结果一致,不一致会警告甚至出错。

CSR 与 SSR 对比

CSR 与 SSR 对比

维度 CSR SSR
首屏内容 等 JS 加载后渲染 HTML 直达
SEO 差,内容在 JS 里 好,内容在 HTML
服务器负载 低(只发静态文件) 高(每次请求渲染)
开发复杂度 高,代码要同构
数据获取 客户端请求 服务端预取

三、SSR 的优势与挑战

优势

更快的首屏加载:FCP(首屏内容绘制)大幅提前,因为 HTML 自带内容。网络差时体验差异尤其明显。

更好的 SEO:爬虫直接拿到完整 HTML,不需要执行 JS 就能索引内容。对内容型站点(博客、新闻、电商)是刚需。

更好的社交分享:分享到微信、微博时,预览卡片抓取 HTML 的 meta 信息,SSR 保证这些信息完整。

挑战

服务器负载增加:每次请求都要跑一遍 React 渲染。高并发场景要配合缓存、流式渲染、CDN。

代码同构:代码要同时在 Node 和浏览器运行。windowdocument 这些浏览器 API 在服务器端不存在,要用守卫判断或只在客户端逻辑里用。

调试变难:错误可能发生在服务器端,也可能在客户端,栈信息分裂。

手写 SSR 的原理预览

虽然实际项目用 Next.js,但理解手写 SSR 的原理能让你看清 Next.js 替你做了什么。核心就两件事:服务器端用 ReactDOMServer 把组件渲染成 HTML 字符串,客户端再水合:

// 服务器端(示意) import { renderToString } from 'react-dom/server'; const html = renderToString(<App />); // 把 html 拼进页面模板返回给浏览器 // 客户端入口(示意) import { hydrateRoot } from 'react-dom/client'; hydrateRoot(document.getElementById('root'), <App />);

renderToString 在 Node 里把组件渲染成 HTML,hydrateRoot 让客户端 React 接管已经存在的 HTML。这两个 API 就是 SSR 的骨架。Next.js 把「什么时候调用 renderToString、数据怎么注入、路由怎么匹配」全部自动化了,但底层就是这条链。

理解了这条链,几个概念就串起来了:水合为什么要求服务端和客户端首屏一致(React 要对齐 DOM 结构)、数据为什么要在渲染前拿到(HTML 需要带上数据)、同构代码为什么难(同一份代码要跑在两个环境)。

四、Next.js:把 SSR 变简单

手写 SSR 要处理 ReactDOMServer、Express 集成、数据预取、水合——工程量大。Next.js 是 React 生态最主流的 SSR 框架,把这些封装成「开箱即用」。

创建项目

npx create-next-app my-next-app cd my-next-app npm run dev

文件路由

Next.js 用文件系统做路由:pages 目录下的文件自动生成对应路由。放一个 about 文件,就得到 /about 页面:

// pages/about.js export default function AboutPage() { return ( <div> <h1>关于页</h1> <p>这是一个 Next.js 页面。</p> </div> ); }

不需要配置路由表——文件的位置就是路由的路径。这是 Next.js 与 React Router 最大的心智差异:路由从「配置」变成「约定」。

页面与元数据

import Head from 'next/head'; function HomePage() { return ( <div> <Head> <title>我的 Next.js 应用</title> <meta name="description" content="一个简单的 Next.js 应用" /> </Head> <main> <h1>欢迎来到 Next.js</h1> <p>这个页面是服务端渲染的。</p> </main> </div> ); } export default HomePage;

Head 组件把 title、meta 注入 HTML 的 head 部分——这是 SEO 友好的关键(title 和 description 会进入搜索引擎索引和分享卡片)。查看页面源码,你会看到 h1 和 p 的内容直接出现在 HTML 里,这就是服务端渲染的证据。

getStaticProps 与静态生成

SSG 模式对应 getStaticProps——构建时取数,生成静态 HTML:

export async function getStaticProps() { const data = await fetchPosts(); return { props: { posts: data } }; } export default function BlogList({ posts }) { return ( <ul> {posts.map(p => <li key={p.id}>{p.title}</li>)} </ul> ); }

构建时执行一次,产出静态 HTML + 数据 JSON。之后每次访问直接返回静态文件,不跑服务器逻辑——这就是 SSG「快且便宜」的原因。博客、文档这类内容固定更新的站点,SSG 是首选项。

动态路由与页面参数

Next.js 的动态路由用文件名里的方括号表示:创建 pages/products/[id].js,就得到 /products/:id 路由。组件里用 useRouter 读取参数:

import { useRouter } from 'next/router'; export default function ProductPage() { const router = useRouter(); const { id } = router.query; return <h1>商品 {id}</h1>; }

SSR 模式下动态页面配合 getServerSideProps,参数还能传给取数逻辑:getServerSideProps 的 context 里有 params,可以直接用来请求对应商品的数据。这样每次访问 /products/123,服务器渲染的都是 123 号商品的数据。

客户端导航

Next.js 的 Link 组件实现客户端导航——点击不整页刷新,只更新内容。这点和 React Router 的 Link 心智一致:

import Link from 'next/link'; export default function Nav() { return ( <nav> <Link href="/">首页</Link> <Link href="/about">关于</Link> </nav> ); }

客户端导航保持了 SPA 的「不刷新切换」体验,同时首屏是 SSR 渲染好的——两条路线的优势被 Next.js 揉在一起:首屏内容直达、后续切换像 SPA 一样顺滑。

💡 关键直觉:Next.js 的核心是「按页面选渲染模式」。它不强制你全站 SSR——登录后的后台可以用 CSR 组件,营销页用 SSG,实时数据页用 SSR,同一个应用里按需混用。把「页面」当成渲染模式的决策单元,是 Next.js 应用设计的起点。

getServerSideProps:服务端取数

需要数据进首屏 HTML 时,用 getServerSideProps 在服务器端获取,通过 props 传给组件:

function HomePage({ data }) { return ( <div> <h1>欢迎</h1> <p>服务器端获取的数据:{data}</p> </div> ); } export async function getServerSideProps() { // 在服务器端请求数据 const data = await fetchData(); return { props: { data } }; } export default HomePage;

getServerSideProps 在服务器端执行,返回的 props 在渲染前注入组件。这样首屏 HTML 里就带上了数据——不用等客户端再请求一次。

💡 关键直觉:getServerSideProps 是「把数据获取从客户端搬到服务器端」的入口。它的收益是首屏 HTML 自带数据、SEO 更好;代价是每次访问都执行服务器端逻辑,动态性强但缓存的难度大。数据「要不要进首屏」决定了你用不用它。

五、SSR 与 SSG:按场景选渲染模式

Next.js 不只做 SSR,还支持 SSG(静态站点生成)和 CSR 混合。三种模式的选择是按页面定的:

模式 渲染时机 适合 代表
SSG 构建时 内容固定、更新不频繁 博客、文档、营销页
SSR 每次请求 内容实时、依赖用户/数据 个人中心、详情页
CSR 客户端 纯交互、无 SEO 需求 后台管理、仪表盘

SOURCE 原文提到 Next.js 支持 SSR 和 SSG 两种预渲染方式,按场景选择。大多数内容型页面用 SSG(快且便宜),需要实时数据的页面用 SSR,纯交互后台用 CSR——这个三角选择是 Next.js 应用的核心决策。

六、部署与生态

Next.js 应用部署到 Vercel(官方推荐)最省事——连接仓库、配置环境变量,自动构建部署。也可以部署到 Netlify、AWS 或其他 Node 服务器。部署时注意环境变量(密钥、API 地址)要配置在部署平台,不要写进代码里。

Next.js 生态还有几个值得了解的配套:API 路由(在 pages/api 下写后端接口)、中间件(请求拦截、重定向)、图片优化组件(内置懒加载与格式转换)。这些让 Next.js 从「SSR 框架」扩展成「全栈框架」。

⚠️ 常见坑:在组件渲染里直接访问 window/document。SSR 时组件在 Node 里渲染,这些 API 不存在,会直接报错。解法是守卫判断「typeof window !== 'undefined'」或把浏览器专属逻辑放进 useEffect(客户端才执行)。

七、什么项目该用 SSR

诚实说,不是所有 React 项目都需要 SSR:

需要 SSR 的:内容型站点(博客、新闻、电商列表)、强 SEO 需求的落地页、首屏体验要求极高的 C 端产品。

不需要的:登录后的后台管理系统(爬虫进不来,用户已登录)、纯工具型应用(无 SEO 需求)、内部系统。

判断问题只有一个:搜索引擎或社交分享需要看到你的内容吗? 需要,SSR/SSG;不需要,纯 CSR 更简单。

常见疑问快答

「SSR 和水合不一致会怎样?」 最典型的表现是「闪烁」——服务器渲染的 HTML 是一套内容,客户端首次渲染算出另一套,React 接管时发现不一致,只能在客户端重渲染,于是用户先看到服务器版本、再瞬间被客户端版本替换。更糟的情况是直接抛错。不一致的头号来源是「客户端独有的随机数或时间」:组件里直接 Math.random()new Date(),服务器和客户端各算各的,结果必然不同。解法是把这类值放进 useEffect 里再更新——首屏统一,客户端挂载后再变。

「SSR 项目里所有组件都能用 window 吗?」 不能。服务器渲染阶段根本没有 window,任何直接引用 window、document 的代码都会抛「window is not defined」。两个常用姿势:一是守卫判断 typeof window !== 'undefined',二是把浏览器专属逻辑放进 useEffect——effect 只在客户端执行。第 3.8 节的错误边界在这里也有用:给可能依赖浏览器 API 的组件包一层边界,服务器端出问题时至少能优雅降级而不是让整页崩掉。

「什么项目值得为 SSG 换 Next.js?」 判断标准是「内容更新频率 × 页面数量」。博客、文档站、营销落地页这类「内容少变、页面多」的站点,SSG 的收益最直接——构建时生成全部静态页,访问时零服务器渲染成本,还能直接交给 CDN。如果项目里只有一两个页面需要 SEO,其他全是登录后的业务系统,为这一两个页面引入整个 Next.js 可能不划算,这时候用页面级 prerender 工具更轻。SSR/SSG 是「内容分发问题」的解法,别把它当成「框架先进性」的象征。

温故知新

  • SSR 本质:服务器渲染组件为 HTML,浏览器直达内容
  • 水合:浏览器下载 JS 后 React 接管已有 DOM,绑定交互
  • 手写 SSR 骨架:renderToString 服务端出 HTML,hydrateRoot 客户端接管
  • SSR 优势:首屏快、SEO 好、分享预览完整
  • SSR 代价:服务器负载高、代码要同构、调试变难
  • Next.js 文件路由:文件位置即路由路径,零配置
  • getServerSideProps:服务端取数,props 注入首屏 HTML
  • getStaticProps:构建时取数出静态页,SSG 模式
  • 动态路由:文件名方括号表达,配合 getServerSideProps 按参数取数
  • Head 组件:管理 title/meta,SEO 关键
  • SSG vs SSR vs CSR:内容固定用 SSG,实时用 SSR,纯交互用 CSR
  • 部署:Vercel 推荐,环境变量放平台
  • 用不用 SSR:SEO/分享需要内容可见才用

下一节看错误边界与异常处理——SSR 让内容更快更全,错误处理让应用在意外面前不崩。渲染错误、事件错误、异步错误三类错误各有各的兜底姿势,一次讲清,应用就离「健壮」更近一步。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U