概念
- 客戶端渲染:在瀏覽器端進(jìn)行渲染,服務(wù)端返回的只是 <div id="app"></div> 的空標(biāo)簽文檔;
- 服務(wù)端渲染:在服務(wù)器就做好數(shù)據(jù)的的拼接,請(qǐng)求返回的 html 是完整的文檔。
客戶端渲染,服務(wù)端渲染對(duì)比
- 客戶端渲染不利于 SEO 搜索引擎優(yōu)化(返回的是空標(biāo)簽);
- 服務(wù)端渲染出來的文檔可以被爬蟲抓取,客戶端異步渲染很難被爬蟲抓?。?/li>
- 服務(wù)端渲染直接將 HTML 字符串傳遞給瀏覽器,大大加快了首屏加載時(shí)間;
- 服務(wù)端渲染占用更多的 CPU 和內(nèi)存資源;
- 在服務(wù)端渲染模式下,一些常用的瀏覽器 API 可能無法正常使用(服務(wù)端沒有 document,window);
- 服務(wù)端渲染只支持 vue 中 beforeCreate 和 created 兩個(gè)生命周期(因?yàn)闆]有 dom,所以不涉及 mounted 等)
SSR 運(yùn)行過程:
- 服務(wù)端渲染只是做首屏的渲染;
- 后續(xù)在瀏覽器中的路由切換邏輯執(zhí)行的是客戶端渲染(前端路由切換頁面,每個(gè)路由返回的首屏是服務(wù)端來做)
- 服務(wù)端渲染使用 Node 服務(wù)來實(shí)現(xiàn)(前后端分離模式)
- 傳統(tǒng)的服務(wù)端渲染是 jsp asp php+smarty,不適合前后端分離開發(fā)模式
整個(gè)打包過程
- 首先要保證客戶端渲染模式下可以跑起來,然后提供一個(gè)服務(wù)端入口
- 通過將一份代碼打包出來兩份邏輯(客戶端和服務(wù)端)
- 前端拿到打包出來的 bandle.js,后端通過打包的結(jié)果渲染出字符串返回給瀏覽器
-
瀏覽器展示 = 前端 bundle.js + 服務(wù)端渲染的字符串
如下圖:展示了打包過程:
安裝包
- vue vue-server-renderer(通過vue-server-renderer來實(shí)現(xiàn)vue的服務(wù)端渲染)
- koa koa-router(通過 node 來做服務(wù)端)
npm install vue vue-server-renderer koa @koa/router -D
開發(fā)中
本地新建一個(gè)文件夾 vue-ssr-demo,初始化項(xiàng)目npm init -f,生成一個(gè)package.json,npm install安裝需要用到的包,新建一個(gè)server.js來啟動(dòng)服務(wù):
// server.js
const Vue = require('vue')
const VueServerRenderer = require('vue-server-renderer')
const vm = new Vue({
data() {
return {
name: '小可愛',
age: 3
}
},
template: `<div>我是:{{name}},{{age}}歲</div>`
})
const Koa = require('koa')
const Router = require('@koa/router')
// 創(chuàng)建一個(gè)渲染器
const render = VueServerRenderer.createRenderer()// 創(chuàng)建一個(gè)渲染器
let app = new Koa() // app實(shí)例
let router = new Router() // 路由實(shí)例
router.get('/', async (ctx) => {
ctx.body = await render.renderToString(vm) // render.renderToString 返回的是一個(gè)promise
})
app.use(router.routes())
app.listen(3500)
啟動(dòng)命令:nodemon server.js,VueServerRenderer.createRenderer()創(chuàng)建一個(gè)渲染器,調(diào)用渲染器 renderToString 方法 傳入vm實(shí)例,訪問http://localhost:3500/ 可以看到頁面展示:

頁面上顯示了
data-server-rendered="true"表示服務(wù)端渲染。
通常我們都是有一個(gè) html 模板,然后將打包的內(nèi)容放進(jìn)去,新建一個(gè) html 文件,寫下vue server 的標(biāo)記
<!--vue-ssr-outlet-->
// index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>vue ssr template</title>
</head>
<body>
<!--vue-ssr-outlet-->
</body>
</html>
server.js 中讀取本地的index.html文件,文件內(nèi)容作為渲染器模板即可:
const Vue = require('vue')
const VueServerRenderer = require('vue-server-renderer')
const vm = new Vue({
data() {
return {
name: '小可愛',
age: 3
}
},
template: `<div>我是:{{name}},{{age}}歲</div>`
})
const Koa = require('koa')
const Router = require('@koa/router')
const fs = require('fs')
const path = require('path')
const htmlStr = fs.readFileSync(path.resolve(__dirname, 'index.html'), 'utf8') // 同步讀取文件
// 創(chuàng)建一個(gè)渲染器
const render = VueServerRenderer.createRenderer({
// 可以從本地讀取html文件當(dāng)作模板
template: htmlStr, // 采用哪個(gè)模版去渲染,在html中加入這個(gè)<!--vue-ssr-outlet-->標(biāo)簽表示渲染到這個(gè)位置上
}) // 創(chuàng)建一個(gè)渲染器
let app = new Koa() // app實(shí)例
let router = new Router() // 路由實(shí)例
router.get('/', async (ctx) => {
// ctx.body = 'hello world'
ctx.body = await render.renderToString(vm) // render.renderToString 返回的是一個(gè)promise
// // <div data-server-rendered="true">我是:hcj,20歲</div>
})
app.use(router.routes())
app.listen(3500)

監(jiān)聽文件改動(dòng),自動(dòng)重啟服務(wù)
npm install nodemon -g
目前實(shí)現(xiàn)了一個(gè)最簡單的 ssr,后續(xù)文章加入客戶端配置,服務(wù)端配置,vue-router, vuex 來實(shí)現(xiàn)完整的 ssr。
github:https://github.com/mxcz213/vue-ssr-demo/tree/part-one
