前言
Ajax 技術(shù)的出現(xiàn)涡拘,讓我們的 Web 應(yīng)用能夠在不刷新的狀態(tài)下顯示不同頁面的內(nèi)容,這就是單頁應(yīng)用据德。在一個單頁應(yīng)用中鳄乏,往往只有一個 html 文件跷车,然后根據(jù)訪問的 url 來匹配對應(yīng)的路由腳本,動態(tài)地渲染頁面內(nèi)容橱野。單頁應(yīng)用在優(yōu)化了用戶體驗(yàn)的同時朽缴,也給我們帶來了許多問題,例如 SEO 不友好水援、首屏可見時間過長等不铆。服務(wù)端渲染(SSR)和預(yù)渲染(Prerender)技術(shù)正是為解決這些問題而生的。
服務(wù)端渲染與預(yù)渲染區(qū)別
客戶端渲染:用戶訪問 url裹唆,請求 html 文件誓斥,前端根據(jù)路由動態(tài)渲染頁面內(nèi)容。關(guān)鍵鏈路較長许帐,有一定的白屏?xí)r間劳坑;
服務(wù)端渲染:用戶訪問 url,服務(wù)端根據(jù)訪問路徑請求所需數(shù)據(jù)成畦,拼接成 html 字符串距芬,返回給前端。前端接收到 html 時已有部分內(nèi)容循帐;
預(yù)渲染:構(gòu)建階段生成匹配預(yù)渲染路徑的 html 文件(注意:每個需要預(yù)渲染的路由都有一個對應(yīng)的 html)框仔。構(gòu)建出來的 html 文件已有部分內(nèi)容
下圖簡單展示了客戶端渲染、服務(wù)端渲染和預(yù)渲染的請求流程拄养。
服務(wù)端渲染與預(yù)渲染共同點(diǎn)
針對單頁應(yīng)用离斩,服務(wù)端渲染和預(yù)渲染共同解決的問題:
- SEO:單頁應(yīng)用的網(wǎng)站內(nèi)容是根據(jù)當(dāng)前路徑動態(tài)渲染的,html 文件中往往沒有內(nèi)容瘪匿,網(wǎng)絡(luò)爬蟲不會等到頁面腳本執(zhí)行完再抓弱斯!;
- 弱網(wǎng)環(huán)境:當(dāng)用戶在一個弱環(huán)境中訪問你的站點(diǎn)時棋弥,你會想要盡可能快的將內(nèi)容呈現(xiàn)給他們核偿。甚至是在 js 腳本被加載和解析前;
- 低版本瀏覽器:用戶的瀏覽器可能不支持你使用的 js 特性顽染,預(yù)渲染或服務(wù)端渲染能夠讓用戶至少能夠看到首屏的內(nèi)容漾岳,而不是一個空白的網(wǎng)頁。
預(yù)渲染能與服務(wù)端渲染一樣提高 SEO 優(yōu)化粉寞,但前者比后者需要更少的配置尼荆,實(shí)現(xiàn)成本低。弱網(wǎng)環(huán)境下仁锯,預(yù)渲染能更快地呈現(xiàn)頁面內(nèi)容耀找,減少頁面可見時間。
什么場景下不適合使用預(yù)渲染
個性化內(nèi)容:對于路由是 /my-profile 的頁面來說,預(yù)渲染就失效了野芒。因?yàn)轫撁鎯?nèi)容依據(jù)看它的人而顯得不同蓄愁;
經(jīng)常變化的內(nèi)容:如果你預(yù)渲染一個游戲排行榜,這個排行榜會隨著新的玩家記錄而更新狞悲,預(yù)渲染會讓你的頁面顯示不正確直到腳本加載完成并替換成新的數(shù)據(jù)撮抓。這是一個不好的用戶體驗(yàn);
成千上萬的路由:不建議預(yù)渲染非常多的路由摇锋,因?yàn)檫@會嚴(yán)重拖慢你的構(gòu)建進(jìn)程丹拯。
Prerender SPA Plugin
prerender-spa-plugin 是一個 webpack 插件用于在單頁應(yīng)用中預(yù)渲染靜態(tài) html 內(nèi)容。因此荸恕,該插件限定了你的單頁應(yīng)用必須使用 webpack 構(gòu)建乖酬,且它是框架無關(guān)的,無論你是使用 React 或 Vue 甚至不使用框架融求,都能用來進(jìn)行預(yù)渲染咬像。
prerender-spa-plugin 原理
那么 prerender-spa-plugin 是如何做到將運(yùn)行時的 html 打包到文件中的呢?原理很簡單生宛,就是在 webpack 構(gòu)建階段的最后县昂,在本地啟動一個 phantomjs,訪問配置了預(yù)渲染的路由陷舅,再將 phantomjs 中渲染的頁面輸出到 html 文件中倒彰,并建立路由對應(yīng)的目錄。
查看 prerender-spa-plugin 源碼 prerender-spa-plugin/lib/phantom-page-render.js莱睁。
// 打開頁面
page.open(url, function (status) {
...
// 沒有設(shè)置捕獲鉤子時待讳,在腳本執(zhí)行完捕獲
if (
!options.captureAfterDocumentEvent &&
!options.captureAfterElementExists &&
!options.captureAfterTime
) {
// 拼接 html
var html = page.evaluate(function () {
var doctype = new window.XMLSerializer().serializeToString(document.doctype)
var outerHTML = document.documentElement.outerHTML
return doctype + outerHTML
})
returnResult(html) // 捕獲輸出
}
...
})
項(xiàng)目實(shí)例
該實(shí)列基于Vue.js 2.0 + vue-router,使用 vue-cli3.0 生成
安裝
npm install prerender-spa-plugin --save
vue.config.js中增加
const PrerenderSPAPlugin = require('prerender-spa-plugin');
const Renderer = PrerenderSPAPlugin.PuppeteerRenderer;
const path = require('path');
module.exports = {
configureWebpack: config => {
if (process.env.NODE_ENV !== 'production') return;
return {
plugins: [
new PrerenderSPAPlugin({
// 生成文件的路徑缩赛,也可以與webpakc打包的一致耙箍。
// 下面這句話非常重要W贰K肘伞!
// 這個目錄只能有一級阅酪,如果目錄層次大于一級旨袒,在生成的時候不會有任何錯誤提示,在預(yù)渲染的時候只會卡著不動术辐。
staticDir: path.join(__dirname,'dist'),
// 對應(yīng)自己的路由文件砚尽,比如a有參數(shù),就需要寫成 /a/param1辉词。
routes: ['/', '/product','/about'],
// 這個很重要必孤,如果沒有配置這段,也不會進(jìn)行預(yù)編譯
renderer: new Renderer({
inject: {
foo: 'bar'
},
headless: false,
// 在 main.js 中 document.dispatchEvent(new Event('render-event')),兩者的事件名稱要對應(yīng)上敷搪。
renderAfterDocumentEvent: 'render-event'
})
}),
],
};
}
}
在main.js中增加
new Vue({
router,
store,
render: h => h(App),
mounted () {
document.dispatchEvent(new Event('render-event'))
}
}).$mount('#app')
router.js 中設(shè)置mode: “history”
預(yù)渲染的單頁應(yīng)用路由需要使用 History 模式而不是 Hash 模式兴想。原因很簡單,Hash 不會帶到服務(wù)器赡勘,路由信息會丟失嫂便。vue-router 啟用 History 模式參考這里。
驗(yàn)證是否配置成功
運(yùn)行npm run build闸与,看一下生成的 dist 的目錄里是不是有每個路由名稱對應(yīng)的文件夾毙替。然后找個 目錄里 的 index.html 用IDE打開,看文件內(nèi)容里是否有該文件應(yīng)該有的內(nèi)容践樱。有的話厂画,就設(shè)置成功了
如果你想修改每個頁面的meta 信息,這里推薦使用 vue-meta(https://github.com/nuxt/vue-meta)
首先生成一個項(xiàng)目并安裝依賴拷邢,組件開發(fā)過程我們不關(guān)注木羹,具體可以查看示例源代碼。
原文鏈接:https://blog.csdn.net/huangjianfeng21/article/details/92421738