前端發展到如今,SPA應該已經被應用的很是廣了。惋惜的是,咱們前進的是快,而人家搜索引擎爬蟲跟用戶的瀏覽器設備還跟不上腳步。辛辛苦苦寫好的單頁應用,結果到了SEO跟瀏覽器兼容這一步懵逼了。javascript
不少同窗確定都想過服務端渲染的問題。然而一看vue、react關於服務端渲染的文檔,可能就被唬住了。以前寫好的並不能無縫遷移。並且,每當有個項目,就須要去run一套node服務。固然,架構能力好些的朋友,能夠作好集中化管理。html
因此,當我想在項目中,採用vue或者react的時候,就遇到這些很是大的阻力。正當我頭疼腦熱的時候呢,我發現了一條新途徑。前端
在前不久呢,同事在羣裏分享了puppeteer,它GitHub的介紹以下:vue
Puppeteer is a Node library which provides a high-level API to control headless Chrome over the DevTools Protocol. It can also be configured to use full (non-headless) Chrome.java
大意就是說,一個提供操做Headless Chrome的API的node庫。node
再具體的說,就是能在node環境中,經過一些API,來「模擬」真實chrome訪問頁面,並對其進行模擬用戶操做、獲取DOM等。react
那既然它可以像真實Chrome那樣去訪問頁面而且輸出渲染後的html,我爲何不能經過它來給咱們作服務端渲染呢?git
設想一下,咱們有這樣一個服務A,它可以像chrome同樣訪問指定頁面,並把最終頁面上的dom返回給你。github
而你本來的業務服務器B,只須要判斷是爬蟲,或者低版本IE來訪問時,調取該服務,獲得html,將html返回給用戶,這就實現了服務端渲染。大體流程圖以下:vue-router
有這樣一個思路後,咱們就想辦法來實踐它。實踐的過程,就是解決問題的過程。仔細想一想,咱們會遇到以下幾個問題:
Q1: 即便是模擬Chrome去請求頁面,不少時候視圖也是異步渲染的。好比先請求列表接口,獲得數據再渲染出列表DOM。這個時間,咱們並無辦法把控。那這個服務,到底時候才應該把加載完成的HTML返回呢?
遇到問題時,首先能夠看看人家的文檔 Puppeteer API。欣喜的是,咱們找到了以下幾個方法:
page.waitFor(selectorOrFunctionOrTimeout[, options[, ...args]]) page.waitForFunction(pageFunction[, options[, ...args]]) page.waitForNavigation(options) page.waitForSelector(selector[, options])
咱們能夠經過一些設定,讓頁面在某種狀況下才返回。好比咱們經過設定 page.waitForSelector('#app')
, 讓頁面出現 id="app"
的元素時,才把html內容返回。
或者經過設定 page.waitForFunction('window.innerWidth < 100')
,當頁面寬度小於100px時,纔將此時的html內容返回。
經過這些方法,咱們就能有辦法控制,想要輸給爬蟲的,是何時、什麼樣的頁面。
Q2: 若是IE用戶訪問量比較大怎麼辦。咱們雖然經過這樣的系統,讓本渲染不出頁面的部分瀏覽器(IE9如下)可以渲染出頁面了。但這樣的請求過程相對而言會更耗時,這不是很合理。
那咱們只要作一個緩存系統便好。每次請求,都會去判斷此請求是否存在未過時的緩存HTML,若是存在,則直接返回緩存HTML,不然再去請求頁面,保存緩存。
Q3: 雖然頁面是出來了,IE用戶仍是沒辦法作一些JS的交互。
這個咱們沒辦法在服務層上去解決了,但咱們能夠在前端上作更友好的交互提示。若是判斷用戶是低版本IE,則出現一個小Tip,提示用戶下載更好的瀏覽器,獲取更好的體驗。
Q4: 單頁應用的路由可能是用錨點(哈希模式)來作的,而哈希參數,服務端沒法獲取,那就沒辦法請求正確的頁面了。
這個有辦法解決,能夠採用HTML History模式的路由,如vue-router,而後路由連接最好以生成a標籤+href的模式寫在頁面中,而不是onclick
後js跳轉,這樣爬蟲能最好的爬取整站頁面。
當問題都想到辦法解決後,咱們就能開始真正coding了。
啪啪啪,啪啪啪 => SSR-SERVICE
好,而後就行了,不到200行的代碼,咱們就實現了一個 通用化的、服務化的、單頁應用服務端渲染解決方案。