利用prerender-spa-plugin提升單頁面應用的體驗 什麼是SSR? SSR有什麼問題? Prerendering 正確的使用姿勢 總結 擴展閱讀

作者:汪楠

簡介: Vue、React單頁面的應用給開發者和用戶都帶來了更好的體驗,但是首屏加載慢、白屏和SEO等問題也日益突出,本文主要針對此用戶體驗問題給出漸進的處理方式-預渲染

目前 Vue React 在前端界混的風生水起,它們的開發思想使得我們能真正做到前後端分離、解耦。單頁面的使用給用戶帶來了更好體驗。不過對於 Vue 和 React 這種框架來說, HTMLinJS 的思路在首屏加載慢、白屏以及 SEO 等問題就日益突出了。

不僅需要拼框架的功能、生態,當然還不能忘記“用戶至上的原理“,拼體驗。孜孜不倦的前端朋友們給出了幾個解決方案:1.Server-side rendering(SSR),2.Prerendering。下面我將一一介紹一下。

SSR 直譯就是服務端渲染,通過設置 SSR ,你就可以在後臺的 Node.js 環境中完成渲染邏輯,然後將 HTML 視圖直接返回給客戶端。這樣你不僅可以使用 Vue React 技術,而且可以直出頁面內容。而非只有一個空殼子在後端那裏。這樣也方便了搜索引擎的蜘蛛獲取頁面,解決 SEO 問題。

你可以回想一下我們在很早之前的前端開發模式,其實就是後端直出頁面。前端重構頁面,交由後端套頁面渲染首頁的數據。當然一些異步的數據,則通過 ajax 獲取數據渲染。這似乎又回到了之前的開發模式。前端和後端還是緊密聯繫在一起了。給維護和迭代帶來的不便。

那它沒有好處嗎?有的!目前,社會上還是有成熟的框架和線上的產品,比如 Nuxt.js [1],

, 說明它還是具有價值的。它很明顯可以解決首屏白屏或者SEO等問題。但是它也引入很多問題,其一,對工程師要求較高,需要同時掌握的前後端知識。其二,要考慮在服務端 Node.js 環境中的內存泄露、運行速度、併發壓力等問題。 Node 層的服務可以用“脆弱”兩個字來形容。其三,開發成本增加,研發週期變長等。一般來說,是需要一個強大且穩定的基礎架構來支撐服務端的壓力。

如果你可以應付這些,無疑 SSR 對於增強應用體驗是非常棒的~,但對於像我這樣有點焦慮的人來說,是否有其他解決辦法呢?有的, Prerendering

有時候,我們開發的單頁面應用也就幾個頁面,非常小型,僅僅是爲了 SEO 、首頁白屏問題,大家都覺得有點校枉過正了。可以利用第三方插件 prerender-spa-plugin [2],在客戶端實現渲染,這樣無需將 Vue 或者 React 代碼部署在服務端。 prerender-spa-plugin Webpack 的插件,它可以編譯應用中的所有靜態頁面,輕而易舉的建立對應的索引路徑。下面結合 Vue.js prerender-spa-plugin 來解決前面所提出的的問題。

安裝

npm install prerender-spa-plugin --save-dev

使用

const PrerenderSPAPlugin = require('prerender-spa-plugin');
const Renderer = PrerenderSPAPlugin.PuppeteerRenderer;

module.exports = {
plugins: [
 new PrerenderSPAPlugin({
      staticDir: path.join(__dirname, 'dist'),
      routes: [ '/', '/about', '/contact' ],
      renderer: new Renderer({
        inject: {
          foo: 'bar'
        },
        renderAfterDocumentEvent: 'render-event'
      })
    })
  ])
]
}

staticDir 指的是預渲染輸出的頁面地址, routes 指的是需要預渲染的路由地址, renderer 則是所採用的渲染引擎是什麼,目前用的是 V3.4.0 版本支持 PuppeteerRenderer inject 則是預渲染過程中都能拿到的值,該值提供給你了機會,讓你覺得是否渲染這部分代碼。例如下面的代碼,是不會被預渲染進 HTML 中的。

showMessage(){
      if(window.__PRERENDER_INJECTED && window.__PRERENDER_INJECTED.foo =='bar') return;
      this.message = '我是測試預加載攔截';
    }

renderAfterDocumentEvent 這個則很關鍵,這個是監聽 document.dispatchEvent 事件,決定什麼時候開始預渲染。

new Vue({
  el: '#app',
  router,
  render: h => h(App),
  mounted () {
    // You'll need this for renderAfterDocumentEvent.
    document.dispatchEvent(new Event('render-event'))
  }
});

實例

具體可以看一下官方的 Vue.js2.0+vue-routerPrerenderSPAExample [3] 實例。

原理及缺點

prerender-spa-plugin 利用了 Puppeteer [4] 的爬取頁面的功能。 Puppeteer 是一個 Chrome 官方出品的 headlessChromenode 庫。它提供了一系列的 API, 可以在無 UI 的情況下調用 Chrome 的功能, 適用於爬蟲、自動化處理等各種場景。它很強大,所以很簡單就能將運行時的 HTML 打包到文件中。原理是在 Webpack 構建階段的最後,在本地啓動一個 Puppeteer 的服務,訪問配置了預渲染的路由,然後將 Puppeteer 中渲染的頁面輸出到 HTML 文件中,並建立路由對應的目錄。

利用官方的實例進行編譯結果如下:

每個對應的路都有一個對應的靜態 HTML 。每一個 HTML 內除了

<div id="app"></div>

這個 Vue 的掛載元素外,還有靜態的標籤內容。

<!DOCTYPE html><html lang="en"><head>
  <meta charset="utf-8">
  <title>PRODUCTION prerender-spa-plugin</title>
<link rel="shortcut icon" href="https://jdc.jd.com/favicon.ico"><style type="text/css"></style><style type="text/css">#app{font-family:Avenir,Helvetica,Arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;text-align:center;color:#2c3e50;margin-top:60px}h1,h2{font-weight:400}ul{list-style-type:none;padding:0}li{display:inline-block;margin:0 10px}a{color:#42b983}</style></head>
<body>
  <div id="app"><div><img src="https://jdc.jd.com/logo.png?82b9c7a5a3f405032b1db71a25f67021"> <h1>Welcome to your prerender-spa-plugin Vuejs 2.0 demo app!</h1> <p>汪楠大大about頁</p> <p><a href="https://jdc.jd.com/" class="router-link-active">Home</a> <a href="https://jdc.jd.com/about" class="router-link-exact-active router-link-active">About</a> <a href="https://jdc.jd.com/contact" class="">Contact</a></p> <ul></ul> <a href="javascript:;">點擊我,看看有什麼效果</a> <p>最好不要點我</p></div></div>
<script type="text/javascript" src="https://jdc.jd.com/build.js"></script>

</body></html>

既然有了每個路由對應的 HTML ,那麼對應 SEO 優化應該不成問題了。我們可以更改 title meta 。而且頁面的內容都已經在 HTML 中直接呈現,就不會有因爲 js 等資源加載慢導致白屏的問題。 prerender-spa-plugin 的確在一定程度上解決了我們對於 SEO 的訴求和頁面加載慢的問題。但是它的缺點還是很明顯的。

  • 不同的用戶看到不同的頁面,動態數據頁面
  • 經常發生變化的頁面,數據實時性展示(比如體育比賽等)
  • 路由過多,構建時間過長

聊了這麼多,大家都是爲了給用戶更好的體驗。基本上我們做的頁面都是強依賴動態數據展示,如果渲染的靜態頁面和最後呈現的頁面之前切換並不自然,那麼體驗是很差的。我們不能爲了解決 SEO 或者加載慢的問題,引入新的問題。那到底該怎麼做呢?其實很簡單: Loading 或者 骨架屏。這兩個都是將過渡的 HTML 片段插入到 <divid="app"></div> 中。一旦 JavaScript 加載完, Vue 開始渲染正式頁面時候,就將把過渡的 HTML 片段幹掉了。看一下下面的代碼

body>
  <div id="app"><div><div class="j-loading-wrap"><div class="j-mask"></div> <div class="j-loading"><img src="https://jdc.jd.com/joy_loading.gif?b494ac2f480615dc87d8797cb1a712da"></div></div> <!----></div></div>
<script type="text/javascript" src="https://jdc.jd.com/build.js"></script>

</body>

<skeleton-loading >
    <row 
        :gutter="{
            bottom: '0.1rem'
        }">
        <column :span="'24'">
            <square-skeleton 
                :boxProperties="{
                    height: '0.3rem'
                }"    
            />
        </column>
    </row>
    <row>
        <square-skeleton 
            :boxProperties="{
                height: '3.1rem'
            }"    
        />
    </row>
<skeleton-loading >

Loading 或者骨架屏只是爲了增強用戶體驗,那麼跟本文的主體有什麼關係呢?有關係,一般在做過渡效果的時候,很多都是手寫代碼,這樣既不利於維護,也不利於統一標準。我們可以使用 prerender-spa-plugin 進行操作,它能把整個頁面都生成靜態文件輸出到 HTML ,那麼對於我們的 Loading 組件或者頁面首屏的 DOM 和樣式自動化的輸出到 HTML 中,不是輕而易舉的事情嗎?並且對於骨架屏來說,它只需要展示首屏的內容,所以我們可以利用插件的全局變量,進行判斷,是否需要後續的抓取頁面動作。這樣也解決了骨架屏的體積大小問題。

再則, SEO 的問題對於目前開發的應用,很少有要求,基本都是入口頁。但是用 prerender-spa-plugin 也可以完美的解決,生成 Loading 的多個路由頁面或者骨架屏的多個路由頁面,都可以由後端部署到 vm 模板中,編寫對應的 title meta ,利用 SEO 的優化。

以上是自動化抓取頁面生成骨架屏,但目前還是處於研究階段,目前我們在生產項目中用的仍然是手寫的骨架屏組件,可以參考這個 vue-skeleton-loading [5] 組件。利用預渲染的插件將骨架屏 Loading 組件或者標準的 Loading 組件以 DOM 形式輸出到部署生產的 HTML 頁面中。整體的Webpack構建環境是採用JDC前端開發部團隊對搭建的vue-cli腳手架Gaea,編寫完loading或者骨架屏部分的代碼後,可以配置輸出的路由地址,利用npm run html 的命令輸出對應的html文件。組件則是來自於JDC前端開發部團隊研發的輕量級的、廣泛使用在京東APP、京東Me等移動端場景的Vue組件庫:NutUI[6]。預渲染插件使得我們能輕鬆的將公用組件插入到html中,從而解決上面說到的問題,不失爲一個漸進的解決方案。

本文羅列了單頁面體驗的痛點:首屏加載慢、白屏的問題以 SEO 。也給出了漸進的解決方案利用預渲染 prerender-spa-plugin 的輸出 Vue 或者 React 公用組件(骨架屏組件和 Loading 組件)到各個路由頁面 HTML 中。後續將依賴預渲染插件進行自動化骨架屏的輸出方案,歡迎討論和交流,敬請關注~

[1] Nuxt.js:https://nuxtjs.org/guide

[2] prerender-spa-plugin:https://github.com/chrisvfritz/prerender-spa-plugin

[3] vue2-webpack-router:https://github.com/chrisvfritz/prerender-spa-plugin/tree/dba55854a95a7a4e9b4aaf4203fb0563739bc58a/examples/vue2-webpack-router

[4] puppeteer: https://github.com/GoogleChrome/puppeteer

[5] vue-skeleton-loading:https://github.com/jiingwang/vue-skeleton-loading

[6] NutUI: http://nutui.jd.com/

文章來源於 全棧探索 微信公衆號,掃描下面二維碼關注:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章