背景
现代化的前端框架(vue、react、angular等)构建的单页应用(SPA)具有用户体验好、渲染性能好、可维护性高等优点。但是同时也存在以下缺陷:
- 首屏加载时间长 ------ 单页应用使用 JavaScript 在客户端生成 HTML 来呈现内容,用户需要等待客户端 JS 解析执行完成才能看到页面,这就使得首屏加载时间变长,从而影响用户体验。
- 不利于SEO ------ 当搜索引擎爬取网站 HTML文件时,单页应用组件化开发使首页的HTML没有内容,因为他它需要通过客户端 JavaScript 解析执行才能生成网页内容。
现代化的服务端渲染提出了一种同构方式,汲取了客户端渲染和服务端渲染的优势来解决这种问题:
- 通过服务端渲染首屏直出,解决首屏渲染慢以及不利于 SEO 问题
- 通过客户端渲染接管页面内容交互得到更好的用户体验
1. 传统服务端渲染
早期Web页面渲染都是在服务端完成的,即服务端运行过程中将所需的数据结合页面模板渲染为 HTML,响应给客户端浏览器并直接展示,接下来我们来写一个 demo,来了解一下传统的服务端渲染:
arduino
// node http服务
npm i express cors -D
// 服务端模板引擎
npm i art-template express-art-template -D
服务端代码:
js
const express = require('express');
const cors = require('cors');
const app = express();
const template = require('art-template');
const fs = require('fs');
app.use(cors());
const productObj = {
code: 200,
message: 'success',
data: [
{
id: 1,
title: 'Apple iPad Pro',
desc:
'苹果 Apple iPad Pro 11英寸平板电脑 2020年新款(256G WLAN版/全面屏/A12Z/Face ID/MXDC2CH/A) 深空灰色',
price: 7029.9,
thumb:
'http://img14.360buyimg.com/n5/s54x54_jfs/t1/93232/3/15628/251953/5e723c47E54d3aff9/9d083eaea3409b83.jpg',
},
{
id: 2,
title: 'HUAWEI Mate 30 Pro 5G',
desc:
'华为 HUAWEI Mate 30 Pro 5G 麒麟990 OLED环幕屏双4000万徕卡电影四摄8GB+256GB丹霞橙5G全网通游戏手机',
price: 6399.0,
thumb:
'http://img14.360buyimg.com/n5/s54x54_jfs/t1/90139/34/1379/180822/5dbe8875E02dc2e95/c78c80a4116ee57d.jpg',
}
],
};
app.use(express.json());
app.get('/getPage', (req, res) => {
const templateStr = fs.readFileSync("./client.html", 'utf-8');
const html = template.render(templateStr, productObj);
res.send(html);
});
app.listen(3000, (val) => {
console.log(`Server is running`);
});
客户端代码:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Title</title>
<style>
* {
margin: 0;
padding: 0;
}
ul li {
display: flex;
flex-wrap: wrap;
margin: 10px 10px 0 10px;
padding: 16px;
border-radius: 10px;
background-color: #f5f5f5;
}
img {
width: 100px;
height: auto;
margin-right: 10px;
}
P {
padding: 10px 0;
}
</style>
</head>
<body>
<ul>
{{ each data }}
<li>
<img src="{{ $value.thumb }}" alt="" />
<div>
<p>{{ $value.title }}</p>
<p>¥{{ $value.price }}</p>
<p>
{{ $value.desc }}
</p>
</div>
</li>
{{ /each }}
</ul>
</body>
</html>
运行测试: 本地启动node服务, 浏览器上输入地址:http://localhost:3000/getPage ,即可看见页面渲染成功: 现在看来,这种渲染模式是不合理的:
- 应用的前后端部分完全耦合在一起,在前后端协同开发方面会有非常大的阻力;
- 前端没有足够的发挥空间,无法充分利用现在前端生态下的一些更优秀的方案;
- 由于内容都是在服务端动态生成的,所以服务端的压力较大;相比目前流行的 SPA 应用来说,用户体验一般;
2. SSR应用的一站式解决方案 ------ nuxt.js
nuxtjs 简单来说就是一个vue的项目融合一个node.js server项目,这里node服务有两个作用:第一点是代替浏览器的工作,笼统理解就是在created 时的请求数据和页面渲染; 第二点是当作静态文件服务器,把渲染好的页面返回给用户
。
创建nuxt.js项目:
js
// 打开自己定义好的项目目录文件,初始化node环境:
npm init -y
// 安装 nuxt
npm i nuxt -D
nuxt.js相关命令说明:
js
nuxt ------ 启动一个热加载的 Web 服务器(开发模式) [localhost:3000](http://localhost:3000/)。
nuxt build ------ 利用 webpack 编译应用,压缩 JS 和 CSS 资源(发布用)。
nuxt start ------ 以生产模式启动一个 Web 服务器 (需要先执行`nuxt build`)。
nuxt generate ------ 编译应用,并依据路由配置生成对应的 HTML 文件 (用于静态站点的部署)。
`--spa` 或 `-s`:禁用服务器端渲染,使用 SPA 模式
nuxt.js项目的发布部署有以下方式:服务端渲染应用部署
、 静态应用部署
和单页面应用程序部署 (SPA)
。服务端渲染应用部署
是每次请求经过服务器,查询数据库或接口,渲染模板后返回html,算是动态渲染的; 静态应用部署
是预先将所有路由页面处理后,生成静态的html,是一个完全不需要服务器的静态网站
- 部署Nuxt.js服务端渲染的应用,应该先进行编译构建,然后再启动 Nuxt 服务,可通过以下两个命令来完成:
sql
nuxt build
nuxt start
- Nuxt.js可依据路由配置将应用静态化,使得我们可以将应用部署至任何一个静态站点主机服务商。可利用下面的命令生成应用的静态目录和文件。这个命令会创建一个
dist
文件夹,所有静态化后的资源文件均在其中。同时需要将nuxt.config.js
中的target
设置为static
。
arduino
npm run generate
- 单页面应用程序部署 (SPA)使用时启用 SPA 模式
mode: 'spa'
或--spa
,并且我们运行打包,生成在导报后自动启动,生成包含常见的 meta 和资源链接,但不包含页面内容。对于 SPA 部署,先将nuxt.config.js
中的mode
更改为spa
。再执行以下命令,自动生成dist/
文件夹。
js
npm run build
2.1 nuxt.js本地实践
js
{
"name": "nuxt-demo",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"dev": "nuxt",
"spa": "nuxt --spa",
"generate": "nuxt generate",
"build": "nuxt build"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"nuxt": "^2.15.8"
}
}
本地nuxt.js项目简单构建好之后,我们将nuxt的相关执行命令集成在在package.json文件的scripts中,通过终端的npm run xxx
来执行。在根目录下新建pages文件夹,Nuxt.js 依据 pages 目录中的所有 *.vue
文件生成应用的路由配置。执行 npm run dev
,Nuxt.js会根据 pages
目录结构自动生成 vue-router
模块的路由配置。
本地访问:http://localhost:3000/ 即index.vue首页展示内容。本文通过nuxt.js搭建简单的SSR项目,更多内容可以参考 nuxt.js中文官网。
3. 搭建自己的SSR服务
笔者将通过vue的简单应用 和 vue的工程化开发的demo来展开对SSR服务应用的理解。
3.1 基于vue的SSR简单应用
首先我们是实现一个简单的WEB服务中渲染Vue的实例:vue-server-renderer
是VUE官方提供的服务端渲染的插件,此处和express作为服务端集成在一起使用,可以做到简单的服务端的渲染。
js
const Vue = require('vue');
const service = require('express')();
// 创建一个 renderer
const renderer = require('vue-server-renderer').createRenderer();
service.get('/', (req, res) => {
// 创建一个 Vue 实例
const app = new Vue({
template: `
<div>{{ message }}</div>`,
data: {
message: 'Hello World',
},
});
// 将 Vue 实例渲染为 HTML
renderer.renderToString(app, (err, html) => {
// 异常时,抛500,返回错误信息,并阻止向下执行
if (err) {
res.status(500).end('Internal Server Error');
return;
}
// 返回HTML
res.end(`
<!DOCTYPE html>
<html lang="en">
<head><title>Hello</title></head>
<body>${html}</body>
</html>
`);
});
});
// 绑定并监听指定主机和端口上的连接
service.listen(3000);
当渲染 Vue 应用程序时,renderer 可以从应用程序生成 HTML 标记。为了简化这些,可以直接在创建 renderer 时提供一个页面模板。多数时候,我们会将页面模板放在特有的文件中。其中<!--vue-ssr-outlet-->
注释在这里起到一个标记作用,将应用程序html 在此标记处处替换,渲染成html页面。
js
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Title</title>
</head>
<body>
<!--vue-ssr-outlet-->
</body>
</html>
js
const Vue = require('vue');
const service = require('express')();
// 创建一个 renderer
const renderer = require('vue-server-renderer').createRenderer({
template: require('fs').readFileSync('./index.template.html', 'utf-8'),
});
const context = {
title: 'vue ssr demo',
metas: `
<meta name="keyword" content="vue,ssr">
<meta name="description" content="vue srr demo">
`,
};
service.get('/', (req, res) => {
// 设置响应头,解决中文乱码
res.setHeader('Content-Type', 'text/html;charset=utf8');
// 创建一个 Vue 实例
const app = new Vue({
template: `
<div>{{ message }}</div>`,
data: {
message: 'Hello World',
},
});
// 将 Vue 实例渲染为 HTML
renderer.renderToString(app, context, (err, html) => {
// 异常时,抛500,返回错误信息,并阻止向下执行
if (err) {
res.status(500).end('Internal Server Error');
return;
}
// 返回HTML, 该html的值 将是注入应用程序内容的完整页面
res.end(html);
});
});
// 绑定并监听指定主机和端口上的连接
service.listen(3000);
然而在实际项目中,不止上述例子那么简单,需要考虑很多方面:路由、数据预取、组件化、全局状态等,所以服务端渲染不是只用一个简单的模板,然后加上使用vue-server-renderer
完成。完整的依赖 vue-server-renderer
做服务端渲染的详细流程和步骤可以参考文章 Vue服务端渲染实践------Web应用首屏耗时最优化方案