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
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,不會全部重新渲染