欲语还休,欲语还休,却道天凉好个秋 ---- 《丑奴儿·书博山道中壁》辛弃疾

什么是 SSR

ShadowsocksR?阴阳师?FGO?

Server-side rendering (SSR)是应用程序通过在服务器上显示网页而不是在浏览器中渲染的能力。服务器端向客户端发送一个完全渲染的页面(准确来说是仅仅是 HTML 页面)。同时,结合客户端的 JavaScript bundle 使得页面可以运行起来。与 SSR 相对的,还有一种 Client-side rendering(CSR)。CSR 和 SSR 的最大区别只是提供 rendering 的是客户端还是服务端,其本质还有一种东西。故以下如果没有着重提出 CSR 和 SSR 不一样的地方,则默认是一致的。

为什么要 SSR

得益于 React 等前端框架的发展,前后端分离,webpack 等编译工具的流行,以及 ajax 实现页面的局部刷新,使得我们现在的应用程序不再像曾经的应用程序一般需要从服务端获取页面,可以动态的修改局部的页面数据,避免页面频繁跳转影响用户体验等问题。也就是 SPA 越来越成为主流应用程序模型。

但是 SPA 的使用,除了以上提到的优势以外,必然会带来劣势。譬如:

  1. 由于需要在页面加载之前就加载所有页面需要的 JavaScript 库,这使得首次打开页面所需要的时间比较久;
  2. 需要研发专门针对于 SPA 的 Web 框架(各种具备 SSR 能力的框架,包括 Next.js 等)
  3. 搜索引擎爬虫
  4. 浏览器历史记录的问题(基于 pushState 的各种 router)

为了解决上述提到的 1. 和 3. 的问题,SSR 开始登上历史的舞台。

SSR 怎么做

基于上述的理论,我们可以设计一个具有 SSR 功能的 React 框架。

首先,我们通过 create-react-app 命令初始化一个 React 项目,可以把初始化完成后的项目理解为具有最简单功能的项目。我们将基于该项目去实现一个 SSR 的功能。

# Yarn
$ yarn create react-app ssr-demo

️ 同学们实践的时候需要注意,当前版本的 cra 命令新建项目的时候,启动会报类似于 Mini.... is not a function的问题。这是因为 mini-css-extract-plugin该插件版本更新导致的,只需要在 package.json里面通过 resolutions 限制mini-css-extract-plugin的版本为 2.4.5 即可

生成项目的目录如下:

./
├── README.md
├── build
├── node_modules
├── package.json
├── public
├── src
└── yarn.lock

已经自动安装完依赖,启动项目我们可以在「本地环境」看到一个最简单的页面。

接下来,我们去实现一个 SSR 功能。首先,我们需要安装 express(如果是 CSR 的话就不需要这一步)

yarn add express

安装完成后,我们需要在 server/index.js文件中编写如下代码

import express from "express";
import serverRenderer from "./serverRenderer.js"; const PORT = 3000;
const path = require("path"); const app = express();
const router = express.Router(); // 当爬虫的请求进来的时候,把所有请求导向 serverRenderer 路由
router.use("*", serverRenderer); app.use(router);
app.listen(PORT, () => console.log(`listening on: ${PORT}`));

其中serverRenderer该文件内容如下:

import React from "react";
import ReactDOMServer from "react-dom/server"; import App from "../src/App"; const path = require("path");
const fs = require("fs"); export default (req, res, next) => {
// 获取当前项目的 HTML 模板文件路径
const filePath = path.resolve(__dirname, "..", "build", "index.html"); // 读取该文件
fs.readFile(filePath, "utf8", (err, htmlData) => {
if (err) {
console.error("err", err);
return res.status(404).end();
} // 借助 react-dom 依赖下的方法将 JSX 渲染成 HTML string
const html = ReactDOMServer.renderToString(<App />); // 将 HTML string 替换到 root 中
return res.send(
htmlData.replace('<div id="root"></div>', `<div id="root">${html}</div>`)
);
});
};

如上,我们完成了一个非常简单的具有 SSR 功能的服务端。

但是仅仅如此是不够的,我们还需要在根目录下,新建parser.js将ESM 转成 CommonJS 运行起来,代码如下:

require("ignore-styles");
require("@babel/register")({
ignore: [/(node_modules)/],
presets: ["@babel/preset-env", "@babel/preset-react"],
}); require("./server");

解释一下上面引入的包的作用:

  • @babel/register:该依赖会将 node 后续运行时所需要 require 进来的扩展名为 .es6、.es、.jsx、 .mjs 和 .js 的文件将由 Babel 自动转换。
  • ignore-styles:该依赖也是一个 Babel 的钩子,主要用于在 Babel 编译的过程中忽略样式文件的导入。

在经过上述的操作之后,我们先 yarn build出我们的产物,然后通过node parser.js来启动 SSR 服务。


经过上述的操作之后,我们设计出了一个非常简单的但合理的 SSR 服务端。作为对比,我们在这里简单的和 Next.js 做对比。

在 Next.js 项目的根目录中的 package.json 中,我们可以看到同样选择了 express 作为服务器.

...
"eslint-plugin-react-hooks": "4.2.0",
"execa": "2.0.3",
"express": "4.17.0",
"faker": "5.5.3",
...

我们可以在 ~/packages/next/server/next.ts文件夹中,发现 Next.js会通过 createServer方法,启动一个 NextServer 对象,该对象负责启动服务器以及渲染模板模板。

命令调用如下:

在 [Next.js](https://nextjs.org/docs/basic-features/pages#server-side-rendering)的官网中,我们可以看到其支持在页面通过 getServerSideProps函数,来实现动态获取接口数据。其实,在大多数支持 SSR 的框架库中,都有类似的设计。因为 SPA 的应用,难免需要通过服务端获取动态数据,并渲染页面,而实现渲染动态数据的 SSR 的设计思路都较为一致。即在该页面的组件同一文件中导出一固定方法,并且 return 某一固定格式。框架会将该数据用作初始数据对页面进行 SSR 渲染。


我们以Next.js为例,了解了 SSR 的大致设计思路,那么接下来我们了解一下 CSR 的大致思路.。

CSR 可以理解为阉割版的 SSR,只实现了 SSR 的预渲染功能。一般用于静态网站,不具备动态获取数据的功能。

CSR 的渲染思路同 SSR 一致,不同点在于 SSR 是需要安装 express而 CSR 不需要安装 express。这也就导致了 CSR 和 SSR 在部署流程上的不同。SSR 项目如 Next.js应用在执行完 build 命令后,可以通过 start 命令执行启动服务器,不再需要配合 nginx 的反向代理。而 CSR 项目如 Umi仍然需要 nginx 的代理。

CSR 最大的不同点在于编译后产物的不同。通常一个前端项目在编译后的产物包括一下:

  • bundle.js或者 chunk.js
  • index.html
  • index.css
  • public/*
  • 其他相关文件,如 rss.xml等

而具备 CSR 的项目通过编译后,会有更多的 HTML文件,这些文件的架构会按照路由生成。譬如:我们目前路由如下:

  • /a
  • /b

分别对应 ComponentA 和 ComponentB,那么在我们编译后产物中会生成a.html和b.html。在我们将产物部署到 nginx 服务上后,就可以实现预渲染功能。

要实现以上功能,最重要的步骤如下:

  • 获取到当前项目的路由
  • 获取到路由对应的组件,如果组件未编译过,需要编译
  • 借助 react-dom 的能力将 JSX 渲染成 HTML,并插入到模板 HTML 中
  • 在编译后产物中根据路由创建文件夹,并将结果 HTML 生成到对应路径中

到这里,我们了解了整个 SSR 的流程,相信大家对 SSR 都有了一定程度的了解。目前社区的绝大部分框架都不需要我们自行去做 SSR。我们了解渲染过程有助于我们在应对各种层出不穷的框架时,能够以不变应万变。

一文带你搞懂 SSR的更多相关文章

  1. 【springcloud】一文带你搞懂API网关

    作者:aCoder2013 https://github.com/aCoder2013/blog/issues/35 前言 假设你正在开发一个电商网站,那么这里会涉及到很多后端的微服务,比如会员.商品 ...

  2. 一文带你搞懂 Kafka 的系统架构(深度好文,值得收藏)

    Kafka 简介 Kafka 是一种高吞吐.分布式.基于发布和订阅模型的消息系统,最初是由 LinkedIn 公司采用 Scala 和 java 开发的开源流处理软件平台,目前是 Apache 的开源 ...

  3. 一文带你搞懂 RPC 到底是个啥

    RPC(Remote Procedure Call),是一个大家既熟悉又陌生的词,只要涉及到通信,必然需要某种网络协议.我们很可能用过HTTP,那么RPC又和HTTP有什么区别呢?RPC还有什么特点, ...

  4. 从定义到AST及其遍历方式,一文带你搞懂Antlr4

    摘要:本文将首先介绍Antlr4 grammer的定义方式,如何通过Antlr4 grammer生成对应的AST,以及Antlr4 的两种AST遍历方式:Visitor方式和Listener方式. 1 ...

  5. 一文带你搞懂 JWT 常见概念 & 优缺点

    在 JWT 基本概念详解这篇文章中,我介绍了: 什么是 JWT? JWT 由哪些部分组成? 如何基于 JWT 进行身份验证? JWT 如何防止 Token 被篡改? 如何加强 JWT 的安全性? 这篇 ...

  6. 一文带你读懂zookeeper在大数据生态的应用

    一个执着于技术的公众号 一.简述 在一群动物掌管的世界中,动物没有人类聪明的思想,为了保持动物世界的生态平衡,这时,动物管理员-zookeeper诞生了. 打开Apache zookeeper的官网, ...

  7. 【项目实践】一文带你搞定Spring Security + JWT

    以项目驱动学习,以实践检验真知 前言 关于认证和授权,R之前已经写了两篇文章: [项目实践]在用安全框架前,我想先让你手撸一个登陆认证 [项目实践]一文带你搞定页面权限.按钮权限以及数据权限 在这两篇 ...

  8. 一文带你读懂什么是vxlan网络

    一个执着于技术的公众号 一.背景 随着云计算.虚拟化相关技术的发展,传统网络无法满足大规模.灵活性要求高的云数据中心的要求,于是便有了overlay网络的概念.overlay网络中被广泛应用的就是vx ...

  9. 实战 | 一文带你读懂Nginx反向代理

    一个执着于技术的公众号 前言 在前面的章节中,我们已经学习了nginx基础知识: 给小白的 Nginx 10分钟入门指南 Nginx编译安装及常用命令 完全卸载nginx的详细步骤 Nginx 配置文 ...

随机推荐

  1. this和super的区别和应用

    A:this和super都代表什么 * this:代表当前对象的引用,谁来调用我,我就代表谁 * super:代表当前对象父类的引用B:this和super的使用区别 * a:调用成员变量  * th ...

  2. 从零搭建react开发环境

    早在六年前,前端开发已经实现了模块化.工程化开发,既然是模块化工程化开发那就少不了包管理工具,所以我们的第一步就是先从安装nodejs开始(安装nodejs携带JavaScript的包管理工具npm) ...

  3. Python入门-系统模块time

    1.time模块 时间戳:1970年,1月1日开始时间元祖:包含日期,时间,保存日期结构的元祖对象格式化时间日期:按照指定的标记进行格式化处理 时间戳 import time time_num = t ...

  4. eBPF Cilium实战(1) - 基于团队的网络隔离

    在 Rainbond 集群中,每个团队对应于底层 Kubernetes 的一个 Namespace ,由于之前使用的底层网络无法进行 Namespace 级别的网络管理,所以在 Rainbond 同一 ...

  5. 一些有用的工具,iftop,iotop,htop,glances

    一些有用的工具: yum install glances -y资源监控工具GLANCESglances 可以为 Unix 和 Linux 性能专家提供监视和分析性能数据的功能,其中包括:CPU 使用率 ...

  6. 面试官:ZAB协议是什么?

    哈喽!大家好,我是小奇,一位不靠谱的程序员 小奇打算以轻松幽默的对话方式来分享一些技术,如果你觉得通过小奇的文章学到了东西,那就给小奇一个赞吧 文章持续更新 一.前言 这天是越来越热了,但是还是有很多 ...

  7. http协议 知识点

    前端工程师,也叫Web前端开发工程师.他是随着web发展,细分出来的行业.第一步要学好HTML.CSS和JavaScript!接着就要学习交互,HTTP协议.Tomcat服务器.PHP服务器端技术是必 ...

  8. Java语言学习day20--7月26日

    ###11抽象类的产生 A:抽象类的产生 a:分析事物时,发现了共性内容,就出现向上抽取.会有这样一种特殊情况,就是方法功能声明相同,但方法功能主体不同.那么这时也可以抽取,但只抽取方法声明,不抽取方 ...

  9. 缓存中间件-Redis(二)

    在上一篇中我们简单总结和介绍了Redis的几个方面 1.使用Redis背景 2.Redis通信多路复用的基本原理 3.Redis基本数据结构 4.Redis持久化方式 这一篇我们使用简单的业务场景来介 ...

  10. python基础练习题(题目 猴子吃桃问题:猴子第一天摘下若干个桃子,当即吃了一半,还不瘾,又多吃了一个第二天早上又将剩下的桃子吃掉一半,又多吃了一个。以后每天早上都吃了前一天剩下的一半零一个。到第10天早上想再吃时,见只剩下一个桃子了。求第一天共摘了多少)

    day13 --------------------------------------------------------------- 实例021:猴子偷桃 题目 猴子吃桃问题:猴子第一天摘下若干 ...