Vue專案預渲染機制引入實踐

SHERlocked93發表於2018-09-02

週末想順便把已經做好靜態頁面的webApp專案做一下SEO優化,由於不想寫蹩腳的SSR程式碼,所以準備採用預渲染,本來想著網上有這麼多預渲染的文章,隨便找個來跟著做不就完了嘛,結果年輕的我付出了整個週末..... 這篇文章就記錄一下最後是怎麼配置的 T.T

宣告:

  1. 以下配置只保留有必要的
  2. 生成目錄這裡使用base代替,請自行修改
  3. vue-cli模板使用webpack,其他模板類推
  4. webApp - 線上預覽
  5. Github - 配置了預渲染的demo

1. 簡介與使用場景

我們知道SPA有很多優點,不過一個缺點就是對(不是Google的)愚蠢的搜尋引擎的SEO不友好,為了照顧這些引擎,目前主要有兩個方案:服務端渲染(Server Side Rendering)、預渲染(Prerending)。

如果你只需要改善少數頁面(例如 /, /about, /contact 等)的 SEO,那麼你可能需要預渲染。無需使用 web 伺服器實時動態編譯 HTML (服務端渲染, SSR),而是使用預渲染方式,在構建時(build time)簡單地生成針對特定路由的靜態 HTML 檔案。它主要使用 prerender-spa-plugin 外掛,其與SSR一樣都可以加快頁面的載入速度,並且侵入性更小,在已上線的專案稍加改動也可以輕鬆引入預渲染機制,而SSR方案則需要將整個專案結構推翻;

訪問預渲染出來的頁面在訪問時與SSR一樣快,並且它將服務端編譯HTML的時機提前到了構建時,因此也降低了服務端的壓力,如果你的伺服器跟我的一樣買的 1M1G1核 的小水管伺服器 ( 窮 ),那麼預渲染可能更適合你。不過SSR和預渲染的使用場景還是有較明顯的區別的。預渲染的使用場景更多是簡單的靜態頁面。服務端渲染適用於複雜、較大型、與服務端互動頻繁的功能型網站,比如電商網站。

2. 安裝配置

首先來看看相關技術棧:vue^2.5.2、vue-router^3.0.1、vue-cli^2.9.6、webpack^3.6.0、prerender-spa-plugin^3.3.0

2.1 安裝

安裝跟其他庫一樣

# Yarn
$ yarn add prerender-spa-plugin -D
# or NPM
$ npm install prerender-spa-plugin --save-dev
複製程式碼

2.2 前端配置

首先看看檔案結構,用的是vue-cli2的webpack模板生成的檔案結構

│  .babelrc
│  index.html
│  package.json
│  README.md
├─build
│      build.js
│      check-versions.js
│      utils.js
│      vue-loader.conf.js
│      webpack.base.conf.js
│      webpack.dev.conf.js
│      webpack.prod.conf.js
├─config
│      dev.env.js
│      index.js
│      prod.env.js
├─src
│  │  App.vue
│  │  main.js
│  │ 
│  ├─assets
│  ├─components
│  ├─router
│  │      index.js
│  ├─styles
│  ├─utils
│  └─views
│          BigData.vue
│          CompanyHonor.vue
複製程式碼

然後是router/index.js的配置,預渲染要求是histroy模式,有的文章說不需要history模式,這是錯的,否則生成的頁面都是同一個html。另外注意加上base否則如果你希望跳轉到二級頁面的localhost/base/home時候,在頁面中點選<router-link to="/home">home</router-link>的時候會跳轉localhost/home

// src/router/index.js

import Vue from 'vue'
import Router from 'vue-router'
 
 
Vue.use(Router)
 
 
export default new Router({
  mode: 'history',
  base: '/base/',
  routes: [...]
})
複製程式碼

然後是config,這裡注意assetsPublicPath不是./,

// config/index.js

const path = require("path")
 
module.exports = {
  build: {
    index: path.resolve(__dirname, "../base/index.html"),
    assetsRoot: path.resolve(__dirname, ".."),
    assetsSubDirectory: "base/static",
    assetsPublicPath: "/",
  }
}
複製程式碼

然後是外掛的配置,是放在prod中的,因為只有build的時候會用

// build/webpack.prod.conf.js

const path = require('path')
const config = require('../config')
const PrerenderSPAPlugin = require('prerender-spa-plugin')
const Renderer = PrerenderSPAPlugin.PuppeteerRenderer
 
const webpackConfig = merge(baseWebpackConfig, {
  new PrerenderSPAPlugin({
   staticDir: config.build.assetsRoot,
   outputDir: path.join(config.build.assetsRoot, 'base'),
   indexPath: config.build.index,
 
   // 對應路由檔案的path
   routes: [
     '/',
     '/BigData',
     '/CompanyHonor'
   ],
 
   renderer: new Renderer({
     headless: false,            // 無桌面系統去掉
     renderAfterDocumentEvent: 'render-event'
   })
  })
})
複製程式碼

注意了,如果你的專案是部署在linux/centOS之類沒有桌面的系統,需要把headless: false去掉,如果centOS報沒有找到lib的錯,請參考 issue-200 的解決辦法。

另外注意上面一個renderAfterDocumentEvent: 'render-event'了麼,這個意思是在render-event事件觸發之後執行prerender,這個事件我們在main.js中mounted鉤子觸發

// src/main.js

import Vue from 'vue'
import App from './App'
 
new Vue({
  el: '#app',
  render: h => h(App),
  mounted() {
    document.dispatchEvent(new Event('render-event'))
  }
})
複製程式碼

還有個配置要注意下在 build/utils.js 中的 ExtractTextPlugin.extractpublicPath ,否則一些vue中引用的資源會找不到

// build/utils.js

ExtractTextPlugin.extract({
  use: loaders,
  fallback: 'vue-style-loader',
  // publicPath: '../../'
})
複製程式碼

這時候執行npm run build就可以生成剛剛配置在PrerenderSPAPlugin外掛中routes中的頁面html了,這過程中會一閃而過的短暫開啟chromium瀏覽器,不用管。

最後生成的目錄樹:

│  index.html
├─BigData
│      index.html
├─CompanyHonor
│      index.html
└─static
    ├─css
    ├─fonts
    ├─img
    └─js
複製程式碼

最後如果希望進一步優化生成出來頁面的SEO,可以配合 vue-meta-info 這個網上有很多文章,就不贅述了

2.3 nginx配置

順便貼一下nginx配置

server {
        listen 80;
        server_name localhost;
        root /nginx-1.14.0/html;

        error_page 500 502 503 504 /50x.html;
        
        location ~ ^/base/ {
          try_files $uri $uri/ /base/index.html;
        }
        location = /50x.html {
            root html;
        }
}
複製程式碼

網上的帖子大多深淺不一,甚至有些前後矛盾,在下的文章都是學習過程中的總結,如果發現錯誤,歡迎留言指出~

參考:

  1. Vue.js 伺服器端渲染指南
  2. Vue 服務端渲染 & 預渲染
  3. vue-cli v3, can't get it to work.. issue #215
  4. unable to start Puppeteer. Failed to launch ... issue #200
  5. When assetsPublicPath is set, the... issue #176
  6. vue.js vue-router history模式路由,域名二級目錄子目錄nginx配置
  7. 處理 Vue 單頁面 Meta SEO的另一種思路

PS:歡迎大家關注我的公眾號【前端下午茶】,一起加油吧~

Vue專案預渲染機制引入實踐

相關文章