一、服务端渲染解决了什么问题?
目前,主流的 C 端网站,它们都会采用服务器端渲染(简称:SSR)的方式去进行网页应用的开发,类似大家熟知的掘金、优快云、博客园,或是淘系官网等。架构选型上也会选择 SSR。那么你有没有想过,为什么 SSR 会广泛用于 C 端 网站 ?
在解释这个问题前,我们需要先来了解一下浏览器渲染一个网页是怎样的一个过程。
1.浏览器是怎么渲染一个页面的?
浏览器渲染一个网页,简单来说可以分为以下几个步骤:
- HTML 解析:在这个过程之前,浏览器会进行 DNS 解析及 TCP 握手等网络协议相关的操作,来与用户需要访问的域名服务器建议连接,域名服务器会给用户返回一个 HTML 文本用于后面的渲染 (这一点很关键,要注意)。
- 渲染树的构建:浏览器客户端在收到服务端返回的 HTML 文本后,会对 HTML 的文本进行相关的解析,其中 DOM 会用于生成 DOM 树来决定页面的布局结构,CSS 则用于生成 CSSOM 树来决定页面元素的样式。如果在这个过程遇到脚本或是静态资源,会执行预加载对静态资源进行提前请求,最后将它们生成一个渲染树。
- 布局:浏览器在拿到渲染树后,会进行布局操作,来确定页面上每个对象的大小和位置,再进行渲染。
- 渲染:我们电脑的视图都是通过 GPU 的图像帧来显示出来的,渲染的过程其实就是将上面拿到的渲染树转化成 GPU 的图像帧来显示。首先浏览器会根据布局树的位置进行栅格化(用过组件库的同学应该不陌生,就是把页面按行列分成对应的层,比如 12 栅格,根据对应的格列来确定位置),最后得到一个合成帧,包括文本、颜色、边框等,最后将合成帧提升到 GPU 的图像帧,进而显示到页面中,就可以在电脑上看到我们的页面了。
相信看到这里,大家对浏览器怎么渲染出一个页面已经有了大致的了解。页面的绘制其实就是浏览器将 HTML 文本转化为对应页面帧的过程,页面的内容及渲染过程与第一步拿到的 HTML 文本是紧密相关的。
而服务器端渲染对 C 端 网站 的优势, 主要也是在于它拿到的 HTML 不同 。 这样的差异,会给 Web 应用带来不同的表现。
SSR和单页的区别?
- 单页直接返回html只带一个空的div
- 服务端渲染会把当前页面所有的dom元素都拿到
2.易传播性: SSR 爬虫精度更高,有利于 SEO
我们知道,相比于 B 端网站,C 端 Web 应用更要求易传播性和交互稳定性。如果B端管理平台,即使在浏览器上搜不到、不好用、卡顿,甚至死机,用户可能会因为公司要求或是选择有限而强制使用。但是没有人可以强迫你使用一个 C 端网站 。
相比 客户端渲染 ,服务端渲染可以有效提高搜索引擎爬取的精度,进而提高网站的易传播性。
浏览器的推广程度,取决于搜索引擎对站点检索的排名,搜索引擎可以理解是一种爬虫,它会爬取指定页面的 HTML,并根据用户输入的关键词对页面内容进行排序检索,最后形成我们看到的结果。
页面渲染过程中,HTML 解析过程中从服务器端拉取的 HTML 并不是页面最终预期的结果,对于一些高级爬虫,会待页面渲染完成后进行页面数据的拉取和关键词匹配,但是也有一些低级爬虫,它们爬取的将是服务器端拉取的 HTML,那么服务器端拉取下来的HTML中包含的实际页面关键词和数据越多,搜索引擎匹配的精度也会越高。
SSR 会在服务器端完成对页面数据数据的请求,将对应数据注入 DOM 一同返回,会得到一个完整可预览的 HTML。以掘金首页举例,可以看到下图服务器端拉取的 HTML 是包含这个页面中所将展示的实际数据。
而对于客户端渲染,数据的拉取将会在客户端完成,请求服务器拿到的 HTML 将是一个空的包含有执行脚本的 HTML,也就是说,客户端渲染页面的服务器响应的 HTML 并不包含页面中实际数据,也可以参考下图一个 B 端管理平台的 HTML 响应。
服务器端渲染和客户端渲染的差异,决定了服务器端渲染在 爬虫 关键词爬取的精准度上会远胜客户端渲染,使得站点更容易获得相关关键词更高的排名。
3.交互稳定性: SSR 更高效
交互稳定性,这个也与服务器端渲染和客户端渲染的 HTML 差异有关。对于客户端渲染,实际的数据需要在执行脚本后请求数据后才可以得到,而对于服务器端渲染,数据请求的过程在在服务器端已经完成了,这就使得服务器渲染将不再需要进行数据请求,可以拥有更短的首屏时间。
所以,官网作为 C 端网站的一种,架构选型上我们也会选取服务器端渲染来进行开发。
4.小结
SSR服务端渲染的好处
- 首屏性能
- 更好的SEO
- 更好的用户体验
二、SSR环境搭建
从0-1搭建环境
-
SSR工程化
-
SSR怎么做?
-
Nextjs
接下来从零开始搭建一个 mvp 的服务器端渲染项目,带大家去深入了解现代 SSR 是如何实现的。
lint 和构建是前端工程化中经常会遇到的问题,针对开发者的不同,代码风格也是有差异的,对于多人开发项目,风格不同的代码往往使项目的可维护性变得糟糕。并且我们知道 JS 是一门弱类型语言,没有类型的 lint ,在大型项目中经常会遇到编译虽然不会报错,但是因类型隐性强转导致的代码效果与预期不符的问题。lint 会帮助我们来排查出这些问题,提高代码质量。一个好的工程会有更完善的 lint机制和 IDE 提示来规范项目的代码规范,降低多人维护对项目易迭代性的折损 。
而构建是每个项目生产环境必不可少的步骤,因为我们项目中可能会用到 ES6 及以上或是 Typescript 等来提升代码的可维护性和开发效率,但是针对不同的浏览器,能支持的 ECMAScript 版本不同且 Typescript 也不可直接运行在浏览器环境,所以,我们需要通过构建来保证生产环境的项目可以顺利在不同浏览器下运行。
1.代码 lint
Lint 从狭义角度上看是代码语法规则,风格上的限制。但从广义上看,项目中涉及到的代码样式、缩进及 commit 规则的限制都属于 lint 的范畴,我们就针对这三个方向学习下,如何建立工程的统一规范。
npx eslint --init
2.webpack配置
合并 配置项 webpack-merge
https://github.com/survivejs/webpack-merge
安装依赖
npm install @babel/preset-env babel-loader ts-loader webpack webpack-merge webpack-cli --save-dev
// 通用的配置
// webpack.base.js
const path = require("path");
module.exports = {
module: {
rules: [
{
test: /.js$/,
loader: "babel-loader",
exclude: /node_modules/,
options: {
presets: ["@babel/preset-env"],
},
},
{
test: /.(ts|tsx)?$/,
use: "ts-loader",
exclude: /node_modules/,
},
],
},
resolve: {
extensions: [".tsx", ".ts", ".js"],
alias: {
"@": path.resolve(process.cwd(), "./src"),
},
},
};
// server需要继承base的配置
// webpack.server.js
const path = require("path");
const { merge } = require("webpack-merge");
const baseConfig = require("./webpack.base.js");
module.exports = merge(baseConfig, {
mode: "development",
entry: "./src/server/index.tsx",
target: "node",
output: {
filename: "bundle.js",
path: path.resolve(process.cwd(), "server_build"),
},
});
3.路由安装
npm install react-router-dom --save
在服务端渲染中要使用
StaticRouter | React Router API Reference
const content = renderToString(
<StaticRouter location={req.path}>
<Routes>
{router.map((item, index) => {
return <Route {...item} />
})}
</Routes>
</StaticRouter>
)
4.路由跳转
a标签或者直接刷新页面走服务端渲染
通过路由跳转走单页面客户端渲染逻辑
const Home = () => {
const navigate = useNavigate();
return (
<div>
<h1>hello-ssr</h1>
<button
onClick={(): void => {
alert("hello-ssr");
}}
>
alert
</button>
<a href="http://127.0.0.1:3000/demo">链接跳转</a>
<span
onClick={(): void => {
navigate("/demo");
}}
>
路由跳转
</span>
</div>
);
};
5.动态设置页面head
npm install react-helmet --save
6.依赖安装
npm install body-parser --save
npm install axios --save
npm install @reduxjs/toolkit redux-thunk react-redux --save