什么是 SSR

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 stringconst 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. 一文带你搞懂从动态代理实现到Spring AOP

    摘要:本文主要讲了Spring Aop动态代理实现的两种方式. 1. Spring AOP Spring是一个轻型容器,Spring整个系列的最最核心的概念当属IoC.AOP.可见AOP是Spring ...

  2. 一文带你搞懂C#多线程的5种写法

    一文带你搞懂C#多线程的5种写法 1.简介 超长警告! 在学习本篇文章前你需要学习的相关知识: 线程基本知识 此篇文章简单总结了C#中主要的多线程实现方法,包括: Thread 线程 ThreadPo ...

  3. RPC框架:一文带你搞懂RPC

    RPC是什么(GPT答) ChatGPT回答: RPC(Remote Procedure Call)是一种分布式应用程序的编程模型,允许程序在不同的计算机上运行.它以一种透明的方式,将一个程序的函数调 ...

  4. 如何查询你电脑的IP地址?一文带你搞懂IP地址

    上一章介绍了数据链路层--以太网数据帧的报文格式(你知道以太网数据帧在网络中如何发送和接收的吗?一文带你搞懂它),本章介绍下网络层--IP地址. 大家都知道计算机都会有一个IP地址,只有配置了IP地址 ...

  5. RPC框架:从原理到选型,一文带你搞懂RPC

    大家好,我是华仔,RPC系列的文章是我去年写的,当时写的比较散,现在重新进行整理.对于想学习RPC框架的同学,通过这篇文章,让你知其然并知其所以然,便于以后技术选型,下面是文章内容目录: RPC 什么 ...

  6. ipv6单播地址包括哪两种类型_IPV6中为啥没有ARP了呢?一文带你搞懂NDP邻居发现协议...

    前言 前面我们介绍了ICMPv6协议 除了提供ICMPv4常用的基本功能之外,还有邻居发现(ND)的功能.一文带你看懂ICMPv6和ICMPv4的区别 那么究竟什么是邻居发现协议(ND)呢? 邻居发现 ...

  7. 一文带你搞懂什么是测试开发!

    需要说明的是,原文发表于作者的公众号中,文章篇幅虽长,但内容朴实.且能帮助读者进一步理解测试开发工作,请读者耐心品完~ 01 开始前说点什么 1. 自我反省 公众号开通了也有两年多了,除了刚开通的那段 ...

  8. 秒杀系统架构分析与实战,一文带你搞懂秒杀架构!

    作者丨猿码道 jianshu.com/p/df4fbecb1a4b 1.秒杀业务分析 正常电子商务流程 (1)查询商品: (2)创建订单: (3)扣减库存: (4)更新订单: (5)付款: (6)卖家 ...

  9. 【MDX】一文带你搞懂SQL Server Analysis Services 的安装和使用

    目录 Step 1: Install developer and management tools 安装 new stand-alone SQL Server installation or add ...

最新文章

  1. Geohash的精度问题
  2. python awk 读文件_测试python awk sed 读取文件指定位置时的性能
  3. abap 字符串处理
  4. 学长毕业日记 :本科毕业论文写成博士论文的神操作20170322
  5. linux怎么装mac系统,Linux/macos系统怎么安装nvm
  6. .NET C/S(WinForm)开发技巧点滴(转)
  7. 线程回顾Thread
  8. swift -自定义返回图片,替换系统图片backItem
  9. 关于 php mysql pdo cannot find driver 解决方案
  10. 手机有信号但是连不上网是怎么回事?
  11. 【数据分析师-数据分析项目案例一】600w+条短租房数据案例分析
  12. Java程序员怎样考察报表工具的开发效率
  13. Scala 与设计模式(四):Factory 工厂模式
  14. 以为精通Java 线程池,看到这些误区,还是年轻了
  15. vue插槽的理解 slot slot-scop,三种插槽方式,默认插槽,具名插槽,作用域插槽
  16. Scrum Master: 应做和不应做的事情
  17. android 下划线edittext,Android实现EditText添加下划线
  18. 在java中画背景图片_JAVA如何添加背景图片?
  19. 央视调查:“芯荒”正帮助国产汽车芯片实现零的突破
  20. python计时器类

热门文章

  1. C语言程序设计谭浩强第五版复习梳理2
  2. python 界面一
  3. 微信开发者工具报跨域问题,以及配置微信开发者工具可跨域
  4. 科学计算机计算坐标编程,Calculator科学计算器的教程
  5. [译] 设计师的决策树
  6. 【超详细Scratch教学课件分享】简单倒计时
  7. 关于onenote右键图片快速裁剪
  8. java 设计模式.pdf 免费下载
  9. PLSQL Developer 12破解注册码(64位
  10. NOIP 停办之后,新认证 CSP-S/J 报名开启【智能快讯】