同构渲染的历史背景

SSR

# Stage1:传统的服务端渲染

最早期,Web 页面渲染都是在服务端完成的,即服务端运行过程中将所需的数据结合页面模板渲染为 HTML,响应给客户端浏览器。所以浏览器呈现出来的是直接包含内容的页面。

image

最重要的是第四步,渲染是在服务端进行的。

# 案例

下面通过Node.js来了解一下这种方式:

  1. 准备模板一个简单的html,还有一个data.json,里面是一些数据
<!--index.html-->
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>传统的服务端渲染</title>
</head>
<body>
  <h1>传统的服务端渲染示例</h1>
  <h2>{{ title }}</h2>
  <ul>
    {{ each posts }}
    <li data-id="{{ $value.id }}"> 
        <h5>{{ $value.title }}</h5>
        <p>{{ $value.body }}</p>
    </li>
    {{ /each }}
  </ul>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// data.json
{
  "posts": [
    {
      "id": 1,
      "title":"title1",
      "body":"hellohellohellohello"
    },
    {
      "id": 2,
      "title":"title2",
      "body":"hellohellohellohello"
    },
    {
      "id": 3,
      "title":"title3",
      "body":"hellohellohellohello"
    },
    {
      "id": 3,
      "title":"title4",
      "body":"hellohellohellohello"
    },
    {
      "id": 4,
      "title":"title5",
      "body":"hellohellohellohello"
    }
  ],
  "title": "我是标题"
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
  1. 安装express
npm init -y
npm i express
1
2
  1. 根目录下创建后端的服务文件index.js
// 引入express模块
const express = require('express')
// 创建一个express实例
const app = express()

// 添加路由,get请求网站根路径的时候,添加一个处理函数
app.get('/',(req,res) => {
  // 使用send给客户端发送相应
  res.send('hello world')
})

// 监听3000端口号,再输出running
app.listen(3000, () => console.log('running...'))
1
2
3
4
5
6
7
8
9
10
11
12
13
  1. 可以在命令行使用node index.js,因为涉及多次重启服务,所以这里建议使用nodemon
npm i -g nodemon
nodemon ./index.js
1
2
  1. 这个时候打开浏览器访问http://127.0.0.1:3000/可以看到输出的内容

image

  1. 下面将html先渲染到页面上
const express = require('express')
// 引入读写文件模块
const fs = require('fs')
const app = express()
app.get('/',(req,res) => {
  // 通过读文件操作,拿到模板字符串,第二个参数需要制定编码,不指定编码默认是buffer二进制
  const tempStr = fs.readFileSync('./index.html','utf-8')
  res.send(tempStr)
})

app.listen(3000, () => console.log('running...'))
1
2
3
4
5
6
7
8
9
10
11

可以看到浏览器中html已经正常显示,且服务端返回的数据也是html

image

  1. 获取数据并且转换成对象
const data = JSON.parse(fs.readFileSync('./data.json', 'utf-8'))
console.log(data)
1
2
  1. 将数据渲染到模板中,安装第三方模板npm i art-template,这个模板的使用是
// 简单介绍使用,看看就行不是例子范围内的东西
const template = require('art-template')
const html = template.render(' hello {{ message }}', {
    message: '世界'
})
console.log(html)
1
2
3
4
5
6

在输出的时候,这个字符串会变成hello 世界,下面正式开始使用

const express = require('express')
const fs = require('fs')
// 导入模板解析模块
const template = require('art-template')
const app = express()

app.get('/',(req,res) => {
  // 1. 获取页面模板
  const tempStr = fs.readFileSync('./index.html','utf-8')
  // 2. 获取数据
  const data = JSON.parse(fs.readFileSync('./data.json', 'utf-8'))
  // 3. 获取渲染结果:数据 + 模板
  // render方法,第一个参数是模板,第二个参数是对应的数据
  const html = template.render(tempStr, data)
  // 将最后生成的渲染结果发送到响应中
  res.send(html)
})

app.listen(3000, () => console.log('running...'))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

然后打开浏览器访问,可以看到数据都渲染到了页面中,响应里面也是

image

# 这种方式存在的问题

这就是最早动态网站的核心工作步骤。在今天看来,这种渲染模式是不合理或者说不先进的。因为在当下这种网页越来越复杂的情况下,这种模式存在很多明显的不足:

  • 应用的前后端部分完全耦合在一起,不利于开发人员开发或者维护。
  • 前端没有足够的发挥空间,无法充分利用现在前端生态下的一些更优秀的方案;
  • 由于内容都是在服务端动态生成的,所以服务端的压力较大;
  • 相比目前流行的 SPA 应用来说,用户体验一般,比单页应用相比每次访问必须进行完整的刷新。

# Stage2:客户端渲染

传统的服务端渲染有很多问题,但是这些问题随着客户端 Ajax 技术的普及得到了有效的解决,Ajax技术可以使得客户端动态获取数据变为可能,实现前后端分离,后端实现数据处理,提供接口,前端实现视图渲染的处理。前后端分离的模式极大提高了开发效率和可维护性。

image

# 优势

  • 异步刷新页面,不用每次获取新数据都刷新页面
  • 前后端分离,极大提高了开发效率和可维护性

# 这种方式存在的问题

  • 首屏渲染慢
  • 不利于SEO

# 为什么首屏渲染慢?

因为 HTML 中没有内容,必须等到 JavaScript 加载并执行完成才能呈现页面内容。先加载服务端给的空html,然后再去执行相关的js文件,里面会去异步的请求服务端获取到数据再将页面动态渲染,这个路程比较长,最少有三个http请求的周期。

# 为什么不利于SEO?

搜索引擎是怎么获取网页内容的?

创建一个js文件并写下面的代码

// 导入node中的http模块
const http = require('http')

// 通过程序获取指定的网页内容
http.get('http://localhost:3000/', res => {
  let data = ''
  res.on('data', chunk => {
    data += chunk
  })
  res.on('end', () => {
    console.log(data)
  })
})
1
2
3
4
5
6
7
8
9
10
11
12
13

执行的时候可以看到,如果是服务端的代码就可以拿到全部的html,如果是SPA页面,首页是什么都没有的。HTML中没有内容,所以对于目前的搜索引擎爬虫来说,页面中没有任何有用的信息,自然无法提取关键词,进行索引了。

# Stage3:现代化的服务端渲染(同构渲染)

# 什么是同构渲染?

同构渲染:也就是【服务端渲染】 + 【客户端渲染】结合的一种模式,将两者的优点结合到一起。

isomorphic web apps(同构web应用):

  • isomorphic/universal
  • 基于 react、vue 框架,客户端渲染和服务器端渲染的结合
    • 在服务器端执行一次,用于实现服务器端渲染(首屏直出)
    • 在客户端再执行一次,用于接管页面交互
  • 核心解决 SEO 和首屏渲染慢的问题

image

# 如何实现同构渲染?

  1. 第一种:使用Vue,React等框架的官方解决方案
    • 优点:有助于理解原理
    • 缺点:需要搭建环境,比较麻烦
  2. 第二种:使用第三方解决方案
    • React生态的Next.js
    • Vue生态的Nuxt.js,是一个基于Vue.js生态开发的一个第三方服务端渲染框架,通过它我们可以轻松构建现代化的服务端渲染应用。

# 同构渲染应用的问题

# 1. 开发条件所限

  • 浏览器特定的代码只能在某些生命周期钩子函数中使用,以vue为例,我们对其生命周期不需要做过多的处理,但是同构渲染应用中,我们要掌握服务端渲染的生命周期也要掌握客户端渲染的生命周期
  • 一些外部扩展库可能需要特殊处理,之前客户端渲染运行的扩展库需要进行特殊处理才能在服务器渲染应用程序中运行,一些服务端扩展库也需要特殊处理。
  • 不能在服务端渲染期间操作DOM
  • 某些代码操作需要区分运行环境

# 2. 涉及构建和部署的要求更多

客户端渲染 同构渲染
构建 仅构建客户端应用即可 需要构建两个端
部署 可以部署在任意Web服务器中
(tomcat,apache,nginx)
只能部署在Node.js Server

# 3. 更多的服务器端负载

  • 在 Node.js 中渲染完整的应用程序,显然会比仅仅提供静态文件的 server 更加大量占用 CPU 资源
  • 高流量环境下使用,请准备相应的服务器负载,并明智地采用缓存策略。
  • 需要更多的服务端渲染优化工作处理

# 服务端渲染使用建议

  • 首选渲染速度是否真的重要?
  • 是否真的需求SEO? 事实上,很多网站是出于效益的考虑才启用服务端渲染,性能倒是在其次。
更新时间: 2022-01-29 23:25