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文檔

1npm i -D prerender-spa-plugin

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

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

4.cdn的坑

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

 1// vue.config.js
2const path = require('path')
3const resolve = dir => path.join(__dirname, dir)
4const PrerenderSPAPlugin = require('prerender-spa-plugin')
5const Renderer = PrerenderSPAPlugin.PuppeteerRenderer
6
7module.exports = {
8    outputDir: process.env.NODE_ENV === 'production' ? resolve('dist') : resolve('/www/statics/'),
9    publicPath: process.env.NODE_ENV === 'production' ? 'http://localhost/' : '/',
10    indexPath: resolve('dist/index.html'),
11    ...
12    if(process.env.NODE_ENV === 'production'){
13        config.plugin('prerender-spa-plugin')
14            .use(PrerenderSPAPlugin, [{
15                staticDir: resolve('dist'),
16                indexPath: resolve('/www/statics/index.html'),
17                routes: ['/''/about/'],
18                postProcess (renderedRoute) {
19                    renderedRoute.html = renderedRoute.html.replace(/http:\/\/localhost/g'http://cdn.path/')
20                    return renderedRoute
21                },
22                renderernew Renderer({
23                    injectProperty'__PRERENDER_INJECTED__',
24                    inject'prerender',
25                    headlessfalse,
26                    renderAfterDocumentEvent'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'
    2import Vue from 'vue'
    3import App from './App.vue'
    4import { createRouter } from './router'
    5import { createStore } from './store'
    6...
    7
    8// prerender.js
    9/* eslint camelcase: "off" */
    10if (process.env.NODE_ENV === 'production') {
    11    let isPrerender = 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'第一行加注釋'/* eslint camelcase: "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,不會全部重新渲染



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM