使用預渲提高SPA應用體驗

前言

在目前的前端領域,單頁web應用(SPA)已經有了比較高的佔有率,比較主流的web框架ReactAngularVue幾乎已經統治了前端市場。html

單頁應用確實帶來了更好的先後端分離,以及用戶體驗好、快,內容的改變不須要從新加載整個頁面等等的優勢,喜憂參半,SPA應用首屏加載慢、白屏以及 SEO 等問題也就慢慢顯露出來。前端

問題的來源是SPA應用採用的是客戶端渲染,DOM節點要等待JS文件加載完畢後纔會生成,因此就浮現了以上幾個問題。vue

爲了解決以上問題,目前有兩個比較主流的解決方案:webpack

  • 服務端渲染(SSR
  • 預渲染(Prerender

服務器端渲染 vs 預渲染 (SSR vs Prerender)

什麼是服務端渲染(SSR)?

當服務器接收到請求後,它把須要的組件渲染成 HTML 字符串,而後把它返回給客戶端(這裏統指瀏覽器)。以後,客戶端會接手渲染控制權。git

優點:github

  • 更好的 SEO,因爲搜索引擎爬蟲抓取工具能夠直接查看徹底渲染的頁面。
  • 更快的內容到達時間 (time-to-content),特別是對於緩慢的網絡狀況或運行緩慢的設備。

問題:web

  • 涉及構建設置和部署的更多要求。
  • 更多的服務器端負載。

目前已經有了比較成熟的服務端渲染應用框架,React有Next.js,Vue有Nuxt.js(文檔十分詳細,社區也挺豐富👍),它們都是由zeit.co 背後的團隊發佈的,固然你也能夠本身構建一套服務端渲染。chrome

什麼是預渲染(Prerender)?

無需使用web 服務器實時動態編譯 HTML,而是使用預渲染方式,在構建時 (build time) 簡單地生成針對特定路由的靜態HTML 文件。npm

若是項目中使用 webpack,你可使用 prerender-spa-plugin 輕鬆地添加預渲染,後面將會具體實現。後端

是否須要?哪一個更適合?

在對你的應用程序使用服務器端渲染 (SSR) 以前,你應該問的第一個問題是,是否真的須要它。這主要取決於內容到達時間 (time-to-content) 對應用程序的重要程度。若是並不過重要,這種狀況下去使用服務器端渲染 (SSR) 將是一個小題大做之舉。

若是假設你須要更好SEO和內容到達時間 (time-to-content) ,若是你使用服務器端渲染 (SSR) 只是用來改善少數頁面,那麼這個時候你可能更須要預渲染,優勢是設置預渲染更簡單,你能夠得到SSR的幾乎全部優勢,無需更改代碼或添加服務器端就能輕鬆實現的解決方案。

在項目中加入預渲染(Prerender)

prerender-spa-plugin能夠給現有項目加入預渲染,咱們就以Vue爲實例進行預渲染優化。

先用Vue官方提供的腳手架3.0搭建一個簡單的Vue項目,步驟就不寫了,具體實現能夠參照官方文檔

1. 安裝prerender-spa-plugin依賴

yarn add prerender-spa-plugin --dev
複製代碼

1.1 坑點

由於這個組件須要依賴Puppeteer,它是是 Google Chrome 團隊官方的無界面(Headless)Chrome 工具,它是一個 Node 庫,提供了一個高級的 API 來控制 DevTools協議上的無頭版 Chrome 。也能夠配置爲使用完整(非無頭)的 Chrome。

鑑於 Puppeteer 須要 Chromium,可是即使你的上網姿式足夠科學,也一樣會遇到安裝失敗的問題,嘗試了不少解決方案,提供一個成功率較高的解決方案。

在你的項目根目錄建立一個.npmrc的文件,固然你也能夠直接修改你本機的.npmrc配置。

// .npmrc
puppeteer_download_host = https://npm.taobao.org/mirrors
複製代碼

而後再嘗試安裝。

2. 建立vue.config.js

// vue.config.js
const path = require('path')
const PrerenderSPAPlugin = require('prerender-spa-plugin')
const Renderer = PrerenderSPAPlugin.PuppeteerRenderer

function resolve (dir) {
  return path.join(__dirname, dir)
}

module.exports = {
  publicPath: './',
  configureWebpack: () => {
    if (process.env.NODE_ENV === 'production') {
      return {
        plugins: [
          new PrerenderSPAPlugin({
            staticDir: resolve('dist'),
            routes: ['/', '/about'], // 你須要預渲染的路由
            renderer: new Renderer({
              inject: {
                _m: 'prerender'
              },
              // 渲染時顯示瀏覽器窗口,調試時有用
              headless: true,
              // 等待觸發目標時間後,開始預渲染
              renderAfterDocumentEvent: 'render-event'
            })
          })
        ]
      }
    }
  }
}
複製代碼

更多詳細的配置能夠查看prerender-spa-plugin官方文檔,根據需求添加。

3. 在生命週期裏調用自定義事件

// main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'

Vue.config.productionTip = false

new Vue({
  router,
  render: h => h(App),
  mounted () {
    // 觸發renderAfterDocumentEvent
    document.dispatchEvent(new Event('render-event'))
  }
}).$mount('#app')
複製代碼

4. 運行打包腳本

yarn run build
複製代碼

沒有使用預渲染打包獲得的dist文件夾目錄:

沒有使用預渲染

使用預渲染後打包獲得的dist文件夾目錄:

使用預渲染

能夠看到多了一個about目錄,裏面有一個html文件。咱們查看一下根目錄的html文件,也就是首頁的html文件。

沒有使用預渲染獲得根目錄html文件

Xnip2019-08-23_15-26-53.jpg

使用預渲染獲得根目錄html文件

Xnip2019-08-23_15-27-44.jpg

部署後預渲染和非預渲染的差異

我把它們都部署到gh-pages上,咱們來看一下差異。

沒有使用預渲染請求到的Document

Xnip2019-08-23_15-06-21.jpg

使用預渲染請求到的Document

Xnip2019-08-23_15-05-38.jpg

能夠看到使用預渲染時初始化的HTML文件已經有了DOM結構,這樣爬蟲就能夠來抓取到DOM結構,SEO優化更好。

錄了兩個GIF點擊刷新體驗下差異,提早在調試工具鉤上Disable cache,每次刷新都不會使用緩存,從新向服務器發起請求。沒有使用預渲染:

no-prerender.gif

使用預渲染:

prerender.gif

能夠看到使用預渲染以後首屏幾乎沒有白屏。

能夠點擊下面連接親自體驗一下,Demo地址:

不足

  • 預渲染的只是快照頁面,不適合頻繁變更的頁面
  • 設置路由越多,構建時間越長

這是我使用時感受比較遺憾的地方,並不必定全面。

總結

我的理解,插件的實現原理是在打包完成以後, 利用了 Puppeteer的爬取頁面的功能,模擬瀏覽器訪問路由,而後把JS生成的DOM結構以HTML靜態文件的形式再保存下來。

確實是漸進式的解決了SPA應用潛在的一些問題,而且比較容易的就能集成到現有的項目,但也有遺憾的地方。

本文只是作了一個簡單的Demo,更多的使用技巧還須要你親手去探索。

參考

prerender-spa-plugin

Vue SSR 指南

Nuxt.js

寫在最後

文中若是有問題和遺漏,歡迎在評論區指出,若是本文能給幫助到你,請給個點贊👍和關注

也能夠點擊我另外的文章:

本文到此結束,886🚀🚀

相關文章
相關標籤/搜索