第六弹:SSR、SSG、CSR
在 APP 应用打包后,根据bundle.js在哪个端运行,以及数据的产生方式(前端请求-后端响应-数据库返回、后端请求-数据库返回-前端接收、前端打包好数据-后端响应)可以分成三种形式的页面渲染方案:
- Client-side rendering(CSR) 客户端渲染
- Server-Side Rendering(SSR)服务端渲染
- Static Site Generation(SSG)静态站点生成
客户端渲染 CSR
客户端渲染模式下,服务端会把渲染需要的静态文件发送给客户端,客户端加载过来之后,自己在浏览器里跑一遍 JS,根据 JS 的运行结果,生成相应的 DOM。这种特性使得客户端渲染的 HTML 源代码总是特别简洁,往往是这样:
html1<!DOCTYPE html> 2<html> 3 <head> 4 <title>我是客户端渲染的页面</title> 5 </head> 6 <body> 7 <div id="root"></div> 8 <script src="bundle.js"></script> 9 </body> 10</html>
我们常见的 Vue、React 等的框架代码就是被打包在bundle.js 中,当浏览器将加载过来的 bundle.js跑一遍后,才将 root 节点内的 DOM 给创造出来。
服务端渲染 SSR
服务端渲染的模式下,当用户第一次请求页面时,由服务器把需要的组件或页面渲染成 HTML 字符串,然后把它返回给客户端。客户端拿到手的,是可以直接渲染然后呈现给用户的 HTML 内容,不需要为了生成 DOM 内容自己再去跑一遍 JS 代码。
使用服务端渲染的网站,可以说是“所见即所得”,页面上呈现的内容,我们在 html 源文件里也能找到。
知乎就是采用 SSR 渲染的,我们打开知乎的首页可以看到它的内容跟服务器传送过来的是一致的。
React 简单实现服务端渲染
以下简单介绍如何使用 React 做服务端渲染,首先我们需要准备好要渲染的组件,它可能是这样的:
VDom.jsx
jsx1import React from 'react' 2 3const VDom = () => { 4 return <div>我是一个被渲染为真实DOM的虚拟DOM</div> 5} 6 7export default VDom
在服务端的入口处,引入这个组件,然后使用 React 提供的 SSR 方法生成 DOMString。
javascript1import express from 'express' 2import React from 'react' 3import { renderToString } from 'react-dom/server' 4import VDom from './VDom' 5 6// 创建一个express应用 7const app = express() 8// renderToString 是把虚拟DOM转化为真实DOM的关键方法 9const RDom = renderToString(<VDom />) 10// 编写HTML模板,插入转化后的真实DOM内容 11const Page = ` 12 <html> 13 <head> 14 <title>test</title> 15 </head> 16 <body> 17 <span>服务端渲染出了真实DOM: </span> 18 ${RDom} 19 </body> 20 </html> 21 ` 22// 配置HTML内容对应的路由 23app.get('/index', function (req, res) { 24 res.send(Page) 25}) 26 27// 配置端口号 28const server = app.listen(8000)
此时,访问 localhost:8000/index路由就可以拿到服务端渲染的内容了。

整体的流程只需要三步:
- 让 React 代码在 Node 环境下跑一遍,生成虚拟 DOM
- 调用 renderToString() 方法将结果转化成真实 DOM
- 最后将 DOM 塞进 HTML 模板中返回给浏览器
服务端渲染解决的问题
- SEO
在 SEO 方面,服务端渲染要优于客户端渲染。
采用客户端渲染有个缺点,需要跑完 JS 脚本才能将所有内容呈现出来,搜索引擎爬虫只能够爬取 HTML 源代码,并不会跑 JS 脚本,这就使得客户端渲染出来的网站内容不能被搜索引擎给收集到。
服务端渲染是直接生成 HTML 源代码,这些现成的内容方便搜索引擎收集,我们的网站也就能够更快地被用户定位到。
-
首屏加载
在客户端渲染模式下,浏览器除了加载基本的 HTML,还要将打包过的 JS 文件执行一遍才能最终将页面渲染出来。在 JS 文件执行的这段时间,用户一直处于等待的状态。
相比之下,服务端渲染就直接将结果呈现给用户,中间环节已经被服务器给做掉了,这样就提高了首屏加载的速度。
服务端渲染的限制场景
服务端渲染本质上就是将浏览器做的事,搬到性能更强的服务器上做了。
通过这样的方式的确可以加快资源呈现的速度,但对服务器的性能开销要求也是极大的。
所以服务端渲染一般是用于对网页加载速度要求极高的动态页面或者对 SEO 有极高需求的网站。
静态站点生成 SSG
SSG 全称是 Static Site Generation ,翻译过来是静态站点生成。在资源构建阶段就直接把结果页面输出HTML文件到磁盘,每次访问直接把 html 返回给客户端。
SSG 跟 SSR 的差别
SSG 跟 SSR 很像,都属于pre-rendering的一种,也就是说当客户端请求资源时,资源已经在服务器上准备好了,直接将资源返回给客户端就能够直接看到数据了,这就叫预渲染。
差别在于,SSG 是预先将静态页面打包为html文件,而SSR则需要服务器实时跑代码生成html文件,对于服务器来说,SSG 的压力小很多,而且更适合数据变化不频繁的场景。
SSG 是介于 CSR 与 SSR 之间的一种技术,它没有 SSR 那么吃服务器性能,也不需要像 CSR 这样需要获取到 JS后通过跑JS代码来生成页面,而且 SEO 友好。
要实现 SSG 并不是很容易,需要考虑到很多因素。
比如,假设我有一段数据,只有在服务端拿到了这些数据,我才能渲染页面。
Next 框架
Next 框架是集 SSG 和 SSR 、CSR 为一体的 React 框架,是React最好的 SSR、SSG 开源解决方案。
它在工程端帮助我们做了大量的工作,比较核心的有:
-
代码拆分
Nextjs 的代码拆分是页面级别的,它会将资源按照
page打包成多个html页面。当用户切换路由时,Nextjs 会把对应页面的资源加载过来。如果仅仅是将资源打包成多个页面,开发者单独使用
webpack也可以做到,这并不是nextjs独有的。 -
Nextjs 简化了 SSR、SSG 的页面与数据融合的难题并给出非常友好的解决方案
比如,在 Nextjs 中,使用
getStaticProps函数,会在打包时执行这里的代码,然后将数据预先打包到data目录下。当用户请求时,这些数据实际上已经在静态资源里面生成了,只需要将这段数据和对应的html文件一起发送给客户端,这样就解决了先拿数据后渲染页面的问题。举个例子,以下代码是在
page/login.tsx中定义一个getStaticProps的代码。js1// page/login.tsx 2export async function getStaticProps() { 3 const allPostsData = ['123'] 4 return { 5 props: { 6 allPostsData, 7 }, 8 } 9}build后,会将所有页面都打包出来,并且额外生成一个data目录,里面存放着对应页面数据的json文件。
文件里的内容是这样的
json1// login.json 2{ "pageProps": { "allPostsData": ["123"] }, "__N_SSG": true }用户请求后,就将这个
JSON文件以及login.html一起发送出去,数据和页面就都有了。
SSG 的限制场景
我们从根源来看,SSG 就是预先将页面打包好,然后存放到服务器里,当用户请求时,直接由服务器将页面数据返回给用户的一种技术。
那么在这种情况下,预渲染的数据就不能非常频繁得变更,最好是跟用户关系不大,且固定的数据。比如某人的博客站点,由于博客文章都是固定的,每个人看到的都一样,那么就可以用 SSG 先将所有页面资源都打包成多个html文件,再存放到服务器中,等有人访问时,由服务器将对应的博客页面发送给用户。
而像新浪微博 Top 榜单 这些数据量大且变动频繁的页面,因为数据变化太快,且跟每个用户息息相关,并且还必须保障 SEO,就只能采取 SSR 了。