1. 程式人生 > 實用技巧 >vue預渲染及其cdn配置

vue預渲染及其cdn配置

VUE SEO方案一 - 預渲染及其cdn配置

專案接入VUE這樣的框架後,看起來真是太漂亮了,奈何與MCV框架比起來,單頁應用程式卻滿足不了SEO的業務需求,首屏渲染時間也是個問題。總不能白學VUE,預渲染和SSR還是要搞起來。

1.原理

為什麼做服務端渲染之前先去了解了預渲染呢?因為預渲染方案相比服務端渲染簡單太多了,而且並不是所有專案都需要服務端渲染的。

預渲染是怎麼實現的呢?原理很簡單,在專案開發完成之後,將有限的需要SEO的頁面挑選出來,藉助prerender-spa-plugin外掛實行一次瀏覽器渲染,再將渲染好的原始碼(.html)打包到專案包中,為這些頁面在服務端額外配置路由(不再返回首頁模板index.html)。這樣這些頁面就有單獨的靜態頁面,從而解決了SEO和首屏問題。

2.適用場景及其優劣

預渲染適合那些SEO需求頁面有限的應用,比如一個功能型的app。當只有一些推廣頁面需要SEO的時候,針對這些頁面預渲染就非常方便了。相反,如果是一個資訊型的app,有成千上萬的文章頁面,這時再去做預渲染就比較坑了。

另外,如果預渲染的頁面中含有一些動態的資料,那麼預渲染的頁面首屏資料不會自動更新(因為那是我們已經事先渲染好的,當然不會和使用者需要的一致)。這種情況就需要事先佈置好骨架屏,等等頁面vue接管後重新渲染正確的資料,以免出現不對的資料。當然如果這些資料是SEO相關的,那預渲染就解決不了了。

所以個人認為,預渲染已經可以解決多數SEO問題已經首屏速度問題了,實在不行再上SSR。

3.開始配置

首先我們需要安裝預渲染外掛prerender-spa-plugin

prerender-spa-plugin文件

1npmi-Dprerender-spa-plugin

對於整個專案都部署在靜態服務根目錄下的只要簡單的配置就好了

 1//vue.config.js
2constpath=require('path')
3constresolve=dir=>path.join(__dirname,dir)
4constPrerenderSPAPlugin=require('prerender-spa-plugin')
5constRenderer=PrerenderSPAPlugin.PuppeteerRenderer
6

7module.exports={
8outputDir:resolve('dist'),
9publicPath:'/',
10indexPath:resolve('dist/index.html'),
11...
12chainWebpack:config=>{
13if(process.env.NODE_ENV==='production'){
14config.plugin('prerender-spa-plugin')
15.use(PrerenderSPAPlugin,[{
16//必須-app出口,和outputDir一致就好.
17staticDir:resolve('dist'),
18//必須-需要預渲染的路由.
19routes:['/','/about/'],
20//必須-模擬瀏覽器渲染
21renderer:newRenderer({
22//渲染時開啟瀏覽器debug
23headless:false,
24//渲染時機,正確的時機保證渲染頁面的完整性
25renderAfterDocumentEvent:'render-event'
26})
27}])
28}
29}
30}
31
32//main.js
33newVue({
34router,
35store,
36render:h=>h(App)
37mounted(){
38//這裡的觸發事件就是renderAfterDocumentEvent中的事件
39document.dispatchEvent(newEvent('render-event'))
40}
41})

4.cdn的坑

大部分情況下,上面的配置是無法滿足專案需求的,因為一般我們會把模板和靜態資源分開部署,使用cdn加速。這時候配置就有一點繁瑣了。

 1//vue.config.js
2constpath=require('path')
3constresolve=dir=>path.join(__dirname,dir)
4constPrerenderSPAPlugin=require('prerender-spa-plugin')
5constRenderer=PrerenderSPAPlugin.PuppeteerRenderer
6
7module.exports={
8outputDir:process.env.NODE_ENV==='production'?resolve('dist'):resolve('/www/statics/'),
9publicPath:process.env.NODE_ENV==='production'?'http://localhost/':'/',
10indexPath:resolve('dist/index.html'),
11...
12if(process.env.NODE_ENV==='production'){
13config.plugin('prerender-spa-plugin')
14.use(PrerenderSPAPlugin,[{
15staticDir:resolve('dist'),
16indexPath:resolve('/www/statics/index.html'),
17routes:['/','/about/'],
18postProcess(renderedRoute){
19renderedRoute.html=renderedRoute.html.replace(/http:\/\/localhost/g,'http://cdn.path/')
20returnrenderedRoute
21},
22renderer:newRenderer({
23injectProperty:'__PRERENDER_INJECTED__',
24inject:'prerender',
25headless:false,
26renderAfterDocumentEvent:'render-event'
27})
28}])
29}
30}
  • 首先我們將輸出模板配置到indexPath: resolve('dist/index.html')

  • 但是我們分離了靜態資源,outputDir: resolve('/www/statics/')

  • 分離了靜態資源後,預渲染入口index.html也會打包到resolve('/www/statics/')中,因此要為PrerenderSPAPlugin配置indexPath: resolve('/www/statics/index.html')

  • 這時,按理我們應該將靜態資源配置為publicPath:http://cdn.path/。但是這樣預渲染的時候因為線上cdn中並沒有對應的資源,因此會渲染失敗。我們需要預先配置一個能夠訪問到本地資源的地址,比如publicPath:http://localhost/(這邊的http://localhost/是我專案中自己開的本地服務),這樣預渲染伺服器就能渲染成功了,但是靜態html中的靜態資源地址都變成了http://localhost/,因此我們在PrerenderSPAPlugin中配置postProcess,在其中將http://localhost/替換為http://cdn.path/,再返回出的html就是擁有正確cdn地址的檔案了,如果其中有多個cdn或者其他需求也可以自行匹配替換。

  • 看似成功了,但是我們發現,頁面中vue接管的時候,webpack中的baseUrl也成了http://localhost/,因此從chunk-vendors.js中非同步載入的元件.js的下載域名也是http://localhost/,因此載入錯誤,所以這邊我們需要額外配置webpack。

  • 首先配置PrerenderSPAPlugin的renderer屬性injectProperty: 'PRERENDER_INJECTED__'`與`inject: 'prerender'`,這樣,在預渲染的時候會配置引數`window.__PRERENDER_INJECTED = prerender

  • 在入口頁面的最上方我們引入配置檔案@/common/prerender.js

     1import'@/common/prerender'
    2importVuefrom'vue'
    3importAppfrom'./App.vue'
    4import{createRouter}from'./router'
    5import{createStore}from'./store'
    6...
    7
    8//prerender.js
    9/*eslintcamelcase:"off"*/
    10if(process.env.NODE_ENV==='production'){
    11letisPrerender=window.__PRERENDER_INJECTED__==='prerender'
    12__webpack_public_path__=isPrerender?'http://localhost/':'http://cdn.path/'
    13}
    14//此配置在預渲染模式下繼續使用'http://localhost/'這樣的本地域名,而正常打包時,這替換為'http://cdn.path/',這樣專案就可以正常運行了
    15//因為'__webpack_public_path__'命名不符合eslint,但是我們又需要此規則,所以'prerender.js'第一行加註釋'/*eslintcamelcase:"off"*/'
    16//同時因為'__webpack_public_path__'變數是webpack的全域性屬性,所以配置:
    17globals:{
    18__webpack_public_path__:'writable'
    19},

5.最後

預渲染的頁面被伺服器返回之後,瀏覽器就會自動抓取頁面資訊,首屏渲染之後,vue引擎啟動並接管頁面,這時vue會根據自己載入的元件.js重新渲染頁面,這顯然是不必要的,因此我們需要在layout-App.vue的根元素(<div>)中新增data-server-rendered="true"屬性,這樣vue就知道dom是服務端渲染的,它會自動比較並更新需要更新的dom,不會全部重新渲染