在開始服務端渲染前,我們先看看它能給我們帶來什么,以及什么時候需要用它。
谷歌和Bing可以很好地索引同步的JavaScript應用。同步在這里是個關鍵詞。如果應用啟動時有一個加載動畫,然后內容通過ajax獲取,那爬蟲不會等待他們加載完成。
這意味著在異步獲取內容的頁面上很需要進行搜索引擎優(yōu)化的時候,服務端渲染就很重要。
用戶可能在網絡比較慢的情況下從遠處訪問網站 - 或者通過比較差的帶寬。這些情況下,盡量減少頁面請求數(shù)量,來保證用戶盡快看到基本的內容。
可以用Webpack的代碼拆分避免強制用戶下載整個單頁面應用,但是,這樣也遠沒有下載個單獨的預先渲染過的HTML文件性能高。
對于世界上的一些地區(qū)人,可能只能用1998年產的電腦訪問互聯(lián)網的方式使用計算機。而Vue只能運行在IE9以上的瀏覽器,你可以也想為那些老式瀏覽器提供基礎內容 - 或者是在命令行中使用 Lynx的時髦的黑客。
如果你只是用服務端渲染來改善一個少數(shù)的營銷頁面(如 首頁,關于,聯(lián)系 等等)的SEO,那你可以用預渲染替換。預渲染不像服務器渲染那樣即時編譯HTML,預渲染只是在構建時為了特定的路由生成特定的幾個靜態(tài)頁面。其優(yōu)勢是預渲染的設置更加簡單,可以保持前端是一個完整的靜態(tài)站。
你用webpack可以很簡單地通過prerender-spa-plugin來添加預渲染,它被廣泛地用在Vue應用上 - 事實上,創(chuàng)建者也是Vue核心團隊成員之一。
準備在行動中體驗服務端渲染吧。服務端渲染(即SSR)聽起來很復雜,不過一個簡單的Node腳本只需要3步就可以實現(xiàn)這個功能:
// 步驟 1:創(chuàng)建一個Vue實例
var Vue = require('vue')
var app = new Vue({
render: function (h) {
return h('p', 'hello world')
}
})
// 步驟 2: 創(chuàng)建一個渲染器
var renderer = require('vue-server-renderer').createRenderer()
// 步驟 3: 將 Vue實例 渲染成 HTML
renderer.renderToString(app, function (error, html) {
if (error) throw error
console.log(html)
// => <p server-rendered="true">hello world</p>
})
這并不困難。當然這個示例比大部分應用都簡單。我們不必擔心:
這個指南的其余部分,我們將探討這些功能怎樣運作。一旦你理解了基礎,我們會提供更多細節(jié)和進一步的示例來幫助你解決意外情況。
如果沒有一個Web服務器,很難說是服務端渲染,所以我們來補充它。我們將構建一個非常簡單的服務端渲染應用,只用ES5,也不帶其他構建步驟或Vue插件。
啟動一個應用告訴用戶他們在一個頁面上花了多少時間。
new Vue({
template: '<div>你已經在這花了 {{ counter }} 秒。</div>',
data: {
counter: 0
},
created: function () {
var vm = this
setInterval(function () {
vm.counter += 1
}, 1000)
}
})
為了適應服務端渲染,我們需要進行一些修改,讓它可以在瀏覽器和Node中渲染:
實現(xiàn)這個需要一點模板:
// assets/app.js
(function () { 'use strict'
var createApp = function () {
// ---------------------
// 開始常用的應用代碼
// ---------------------
// 主要的Vue實例必須返回,并且有一個根節(jié)點在id "app"上,這樣客戶端可以加載它。
return new Vue({
template: '<div id="app">你已經在這花了 {{ counter }} 秒。</div>',
data: {
counter: 0
},
created: function () {
var vm = this
setInterval(function () {
vm.counter += 1
}, 1000)
}
})
// -------------------
// 結束常用的應用代碼
// -------------------
}
if (typeof module !== 'undefined' && module.exports) {
module.exports = createApp
} else {
this.app = createApp()
}
}).call(this)
現(xiàn)在有了應用代碼,接著加一個 html文件。
<!-- index.html -->
<!DOCTYPE html>
<html>
<head>
<title>My Vue App</title>
<script src="/assets/vue.js"></script>
</head>
<body>
<div id="app"></div>
<script src="/assets/app.js"></script>
<script>app.$mount('#app')</script>
</body>
</html>
主要引用assets文件夾中我們先前創(chuàng)建的app.js,以及vue.js文件,我們就有了一個可以運行的單頁面應用
然后為了實現(xiàn)服務端渲染,在服務端需要加一個步驟。
// server.js
'use strict'
var fs = require('fs')
var path = require('path')
// 定義全局的Vue為了服務端的app.js
global.Vue = require('vue')
// 獲取HTML布局
var layout = fs.readFileSync('./index.html', 'utf8')
// 創(chuàng)建一個渲染器
var renderer = require('vue-server-renderer').createRenderer()
// 創(chuàng)建一個Express服務器
var express = require('express')
var server = express()
// 部署靜態(tài)文件夾為 "assets"文件夾
server.use('/assets', express.static(
path.resolve(__dirname, 'assets')
))
// 處理所有的Get請求
server.get('*', function (request, response) {
// 渲染我們的Vue應用為一個字符串
renderer.renderToString(
// 創(chuàng)建一個應用實例
require('./assets/app')(),
// 處理渲染結果
function (error, html) {
// 如果渲染時發(fā)生了錯誤
if (error) {
// 打印錯誤到控制臺
console.error(error)
// 告訴客戶端錯誤
return response
.status(500)
.send('Server Error')
}
// 發(fā)送布局和HTML文件
response.send(layout.replace('<div id="app"></div>', html))
}
)
})
// 監(jiān)聽5000端口
server.listen(5000, function (error) {
if (error) throw error
console.log('Server is running at localhost:5000')
})
這樣就完成了。整個示例,克隆下來深度實驗。一旦它在本地運行時,你可以通過在頁面右擊選擇頁面資源(或類似操作)確認服務選渲染真的運行了??梢栽赽ody中看到:
<div id="app" server-rendered="true">You have been here for 0 seconds.</div>
代替:
<div id="app"></div>
Vue還支持流式渲染,優(yōu)先選擇適用于支持流的Web服務器。允許HTML一邊生成一般寫入相應流,而不是在最后一次全部寫入。其結果是請求服務速度更快,沒有缺點!
為了使上一節(jié)應用代碼適用流式渲染,可以簡單的替換 server.get('*',...)為下面的代碼:
// 拆分布局成兩段HTML
var layoutSections = layout.split('<div id="app"></div>')
var preAppHTML = layoutSections[0]
var postAppHTML = layoutSections[1]
// 處理所有的Get請求
server.get('*', function (request, response) {
// 渲染我們的Vue實例作為流
var stream = renderer.renderToStream(require('./assets/app')())
// 將預先的HTML寫入響應
response.write(preAppHTML)
// 每當新的塊被渲染
stream.on('data', function (chunk) {
// 將塊寫入響應
response.write(chunk)
})
// 當所有的塊被渲染完成
stream.on('end', function () {
// 將post-app HTML寫入響應
response.end(postAppHTML)
})
// 當渲染時發(fā)生錯誤
stream.on('error', function (error) {
// 打印錯誤到控制臺
console.error(error)
// 告訴客服端發(fā)生了錯誤
return response
.status(500)
.send('Server Error')
})
})
這不比之前的版本復雜,甚至這對你來說都不是個新概念。我們做了:
Vue的服務端渲染默認非???,但是你可以通過緩存渲染好的組件進一步提高性能。這被認為是一種先進的功能,但是,如果緩存了錯誤的組件(或者正確的組件帶有錯誤的內容)將導致應用渲染出錯。特別注意:
不應該緩存組件包含子組件依賴全局狀態(tài)(例如來自vuex的狀態(tài))。如果這么做,子組件(事實上是整個子樹)也會被緩存。所以要特別注意帶有slots片段或者子組件的情況。
在警告情況之外的,我們可以用下面的方法緩存組件。
首先,你需要提供給渲染器一個 緩存對象。這有個簡單的示例使用 lru-cache
var createRenderer = require('vue-server-renderer').createRenderer
var lru = require('lru-cache')
var renderer = createRenderer({
cache: lru(1000)
})
這將緩存高達1000個獨立的渲染。對于更進一步緩存到內容中的配置,看lru-cache設置
然后對于你想緩存的組件,你可以為他們提供:
例如:
Vue.component({
name: 'list-item',
template: '<li>{{ item.name }}</li>',
props: ['item'],
serverCacheKey: function (props) {
return props.item.type + '::' + props.item.id
}
})
任何純組件可以被安全緩存 - 這是保證給任何組件傳遞一樣的數(shù)據(jù)產生相同的HTML。這些場景的例子包括:
現(xiàn)在,應該理解服務端渲染背后的基本概念了。但是,構建過程、路由、Vuex每一個都有自己的注意事項。
要真正掌握復雜應用下的服務端渲染,我們推薦深度熟悉以下資源:
更多建議: