认识SSR,从《传统的服务端渲染》到《现代化的服务端渲染》有何不同?
最近我们系统的首屏加载较慢,大概要3-4s白屏,产品一直diss我们,让我们优化。之前就有听说SSR可以优化首屏加载,所以就趁此认真看了看SSR到底是什么,看完之后果断拒绝产品这个“无理要求”😂😂😂。其实要不要用SSR还是要综合考虑,对我们当前这个项目而言使用SSR,有点得不偿失。
SPA单页面应用
1. 什么是单页面应用?
单页面应用,SPA
全称 Single Page Application
,一般也称为 CSR
(Client Side Render)
,即客户端渲染。它所需的资源,如 HTML
、CSS
和 JS
等,在第一次请求初始化时加载,加载完成后便不会再重新渲染整个页面。对于 SPA
来说,页面的切换就是组件或视图之间的切换。
2. 什么是多页面应用?
多页面应用 ,MPA
全称MultiPage Application
,指有多个独立页面的应用(多个html
页面),每个页面必须重复加载js
、css
等相关资源。对于 MPA
来说,页面的切换需要整页资源刷新。
3. SPA 的优缺点
优点
- 用户体验好
- 开发效率高
- 渲染性能好
- 可维护性高
缺点
- 首屏渲染时间长
- 不利于seo
4. 解决方案 - 同构渲染(现代化的服务端渲染)
针对SPA
应用的首屏渲染时间长和不利于SEO
检索的问题,可利用服务端渲染(SSR
)进行优化
传统的服务端渲染
早期的
web
页面渲染都是在服务端进行的
一. 传统的服务端渲染流程
二. 通过Node.js演示传统的服务端渲染模式
1. 新建项目
# 新建一个ssr-study的文件夹,存放此次学习的代码
$ mkdir ssr-study
$ cd ssr-study
新建文件夹node-server-rendering
# 创建 node-server-rendering 文件夹
$ mkdir node-server-rendering
$ cd node-server-rendering
在node-server-rendering
目录下,新建data.json
文件和index.html
文件
data.json
文件用于模拟数据库数据
{
"title":"前端三剑客",
"posts":[
{
"id":1,
"title":"HTML",
"content":"HTML的全称为超文本标记语言,是一种标记语言。\n它包括一系列标签,通过这些标签可以将网络上的文档格式统一,使分散的Internet资源连接为一个逻辑整体。\nHTML文本是由HTML命令组成的描述性文本,HTML命令可以说明文字,图形、动画、声音、表格、链接等。"
},
{
"id":2,
"title":"CSS",
"content":"层叠样式表(英文全称:Cascading Style Sheets)是一种用来表现HTML(标准通用标记语言的一个应用)或XML(标准通用标记语言的一个子集)等文件样式的计算机语言。\nCSS不仅可以静态地修饰网页,还可以配合各种脚本语言动态地对网页各元素进行格式化。"
},
{
"id":3,
"title":"JavaScript",
"content":"JavaScript(简称“JS”)是一种具有函数优先的轻量级,解释型或即时编译型的编程语言。\n虽然它是作为开发Web页面的脚本语言而出名,但是它也被用到了很多非浏览器环境中,JavaScript基于原型编程、多范式的动态脚本语言,并且支持面向对象、命令式、声明式、函数式编程范式。"
}
]
}
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>
</body>
</html>
2. 使用express
创建服务端
安装express
,使用express
来创建web
服务。
# 安装express
$ npm i express
在node-server-rendering
目录下创建后端服务文件index.js
const express = require('express')
// 创建一个express实例
const app = express()
// 添加路由
app.get('/',(req,res) => {
res.send('hello world')
})
app.listen(3000,() => console.log('server running...'))
3. 启动服务
在控制台使用node index.js
命令启动服务
但是为了方便,我们也可以使用nodemon
启动服务,nodemon
可以在我们每次写完代码保存之后自动重启服务端。
# 全局安装nodemon
$ npm i nodemon -g
# 使用nodemon启动服务
$ nodemon index.js
服务启动成功后,打开浏览器,访问localhost:3000
,可以收到服务端的hello world
。
4. 把模版和数据结合
现在我们把第1步编写的html
模版与data.json
获取的数据进行结合,我们使用nodejs
的fs
模块读取文件,可以获取到模板html
和数据JSON
文件,然后借助第三方插件art-template
进行渲染。
# 安装第三方渲染引擎:art-template
$ npm i art-template
// index.js
const express = require('express')
const fs = require('fs') // ++
const template = require('art-template') // ++
// 创建一个express实例
const app = express()
// 添加路由
app.get('/', (req, res) => {
// 1. 获取页面模版 - 使用nodejs的fs模块读取文件,
// 为了简单起见,此处使用readFileSync同步方法读取文件
const templateStr = fs.readFileSync('./index.html','utf-8')
// fs.readFileSync默认是二进制文件流,所以要传入第二个参数utf-8,转换成字符串
// 2. 获取数据
const data = JSON.parse(fs.readFileSync('./data.json','utf-8'))
// 3. 渲染:数据 + 模版 = 渲染结果
// render方法接收两个参数,第一个是要渲染的模版字符串,第二个是数据对象;返回一个html
const html = template.render(templateStr, data)
// 4. 把渲染结果发送给客户端
res.send(html)
})
app.listen(3000, () => console.log('server running...'))
使用art-template
模版语法改写模版文件index.html
,
具体的art-template
用法可参考☞aui.github.io/art-templat…
<!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>
<!-- 将数据里的title渲染到这里 -->
<h2>{{ title }}</h2>
<ul>
<!-- each 循环 -->
{{ each posts }}
<!-- $value 获取循环的每一项 -->
<li>{{ $value.title }}</li>
<!-- /each 循环结束 -->
{{ /each }}
</ul>
</body>
</html>
刷新浏览器,可以看到渲染的结果
5. 传统服务端渲染存在的问题
通过这个模拟过程,可以发现传统服务端渲染存在的一些问题:
- 前后端代码完全耦合在一起,不利于开发和维护
- 前端没有足够发挥空间
- 服务端压力大
- 用户体验一般
客户端渲染
客户端渲染得益于
AJax
技术的发展,Ajax
使得客户端动态获取数据成为可能
客户端渲染实现了前后端分离
- 后端:负责处理数据接口
- 前端:负责将接口数据渲染到页面中
一、客户端渲染流程
二、使用Vue项目进行客户端渲染
1. 新建一个Vue项目
此处是用Vue-Cli创建的Vue2项目,也可以用Vue3来体验
# 在ssr-study目录下
# 新建vue2项目
$ vue create vue-client-rendering
# 安装router (可以在创建项目时,勾选安装,若未勾选可以使用以下命令安装)
$ vue add router
2. 修改Vue项目
- 修改App.vue文件
<template>
<div id="app">
<nav>
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link>
</nav>
<router-view/>
</div>
</template>
- 复制之前的`data.json`文件放在`public`目录下
- 安装`axios`并修改`views/HomeView.vue`
# 安装axios
$ npm i axios
<template>
<div class="home">
<h2>{{ title }}</h2>
<ul>
<li v-for="post in posts" :key="post.id">{{ post.title }}</li>
</ul>
</div>
</template>
<script>
import axios from 'axios'
export default {
name:'App',
data(){
return {
title:'',
posts:[]
}
},
async created(){
const {data} = await axios({
method:'GET',
url:'../data.json'
})
this.title = data.title
this.posts = data.posts
}
}
</script>
3. 启动项目
使用命令npm run serve
启动项目,
打开浏览器:localhost:8080
4. 为什么首屏渲染慢?
在快速网络的情况下,针对服务端渲染(≈8ms
)和客户端渲染(≈12ms
)的首屏加载时间差距并不是很大。
所以我们利用浏览器调试工具来模拟慢速网络下,看二者首屏加载的时间。
- 客户端渲染
SPA
首屏加载耗时大约17s
,有三次http
请求周期 - 首先加载空的
html
文件使用了2.02s
- 加载
js
文件使用了13.30s
(这步加载结束时,页面已经渲染出了Home|About
) ajax
请求数据使用了2.05s
(请求到数据后才渲染出前端三剑客的内容)
- 服务端渲染首屏加载耗时大约
2s
- 服务端渲染只需要有一次
http
请求周期,即请求渲染好的html
文件
5. SPA
为什么不利于SEO
?
-
搜索引擎说怎么获取网页内容的?
使用
nodejs
简单模拟搜索引擎的工作// 新建seo/index.js文件 // 搜索引擎说怎么获取网页内容的? const http = require('http') // 搜索引擎 - 获取网页内容、分析、收录 // 通过程序获取指定的网页内容 // http.get('http://localhost:8080/',res => { http.get('http://localhost:3000/',res => { let data = '' res.on('data', chunk => { data += chunk }) res.on('end',() => { console.log(data); }) })
-
获取
localhost:3000
的服务端渲染的网页 -
获取
localhost:8080
的客户端渲染的网页客户端的内容,是必须要解析执行
chunk-vendors.js
和app.js
才能渲染出网页的内容
现代化的服务端渲染 (同构渲染)
同构渲染 = 后端渲染 + 前端渲染
-
基于
React
、Vue
等框架,客户端渲染和服务端渲染的结合- 在服务器端执行一次,用于实现服务端渲染(首屏直出)
- 在客户端再执行一次,用于接管页面交互
-
核心解决
SEO
和首屏渲染慢的问题 -
拥有传统服务端渲染的优点,也有客户端渲染的优点
一、现代服务端渲染的流程
二、通过Nuxt
体验同构渲染
React
生态Next.js
Vue
生态Nuxt.js
1. 新建Nuxt项目
# 新建目录
$ mkdir ssr
$ cd ssr
# 生成package.json文件
$ npm init -y
# 安装 nuxt2
$ npm i nuxt@2
# 修改package.json文件
添加启动脚本命令
...
"scripts": {
"dev":"nuxt"
}
...
2. 创建pages
目录,并在该目录下创建index.vue
Nuxt.js
会依据pages
目录中的所有*.vue
文件生成应用的路由配置。
<template>
<div class="home">
<h2>{{ title }}</h2>
<ul>
<li v-for="post in posts" :key="post.id">{{ post.title }}</li>
</ul>
</div>
</template>
<script>
import axios from 'axios'
export default {
name:'App',
// Nuxt中特殊提供的一个钩子函数,专门用于服务端渲染获取数据
async asyncData({isDev, route, store, env, params, query, req, res, redirect, error}) {
const { data } = await axios({
method:'GET',
url:'http://localhost:3000/data.json'
})
return data
}
}
</script>
我们用到了axios
,还是需要安装一下axios
,npm i axios
。
3. 启动服务 npm run dev
可以看到内容是直接有服务端渲染的,在返回的
HTML
文件里有这些元素内容。
4. 它还是单页面应用吗?
新建layouts
文件夹,在里面新建default.vue
文件
<template>
<div>
<ul>
<li>
<!-- 类似于router-link -->
<nuxt-link to="/">Home</nuxt-link>
</li>
<li>
<nuxt-link to="/about">About</nuxt-link>
</li>
</ul>
<!-- 子页面出口 -->
<nuxt/>
</div>
</template>
<script>
export default {}
</script>
在pages
文件夹里,新建一个about.vue
文件
<template>
<div>
<h1>About</h1>
</div>
</template>
<script>
export default {}
</script>
重启项目,此时我们通过Home
和About
切换,可以观察到页面没有刷新,所以它依旧是单页面应用程序。
三、同构渲染的问题
-
开发条件所限
- 浏览器特定的代码只能在某些生命周期钩子函数中使用
- 一些外部扩展库可能需要特殊处理才能在服务端渲染应用中运行
- 不能在服务端渲染期间操作
DOM
-
涉及构建设置和部署的更多要求
- 只能部署在
Node.js Server
- 只能部署在
-
更多的服务器负载
所以是否真的需要服务端渲染,要取决于“首屏渲染速度是否对我们的项目很重要”,或者是“有SEO的需求”。
转载自:https://juejin.cn/post/7253581299058753595