系列文章:html
從單頁應用(SPA)到服務器渲染(SSR)(本文)node
我的博客以前已經將 vue-router 的模式改成了 history
,即 url 中不包含 hash
,再經過將全部的靜態請求轉發到 index.html,使它看上去彷佛像一個靜態多頁的網站。webpack
然而,它其實和其餘的 SPA (Single Page Application 單頁應用)來講沒有任何的區別,最終是經過前端的路由去控制頁面的顯示。單頁應用雖然在交互體驗上比傳統多頁更友好,但它也有一個天生的缺陷,就是對搜索引擎不友好,不利於爬蟲爬取數據。git
正所謂成也蕭何,敗也蕭何。github
講人話就是,搜索引擎搜不到個人博客啊~哭...web
那什麼對搜索引擎和爬蟲友好的哪?答案就是靜態頁,而非瀏覽器渲染,這就須要服務器直接渲染,也就 SSR(Server Side Render)。vue-router
SSR,服務器渲染。簡單來講就是,服務器將每一個要展現的頁面都運行完成後,將整個相應流傳送給瀏覽器,全部的運算在服務器端都已經完成,瀏覽器只須要解析 HTML 就行。segmentfault
提及來簡單,那到底該如何着手將項目改形成 SSR,和曾經的多頁又有什麼區別哪?既然本身在 SSR 方面是個小白,天然要先從查資料看文檔入手,Vue 2.0 的文檔中有一章就是關於 SSR。
看了文檔以後,它給了我一個新思路,能夠在無須大幅修改原先代碼的狀況下作到 SSR,又不失單頁良好的體驗。
聽上去很酷是否是,具體怎麼作繼續看下去。
一個普通的單頁應用一般是經過 webpack 將源代碼打包後插入到 html 中,當頁面請求時,返回 html 再加載打包後的 js 文件,也就是下圖中的 Application Code,Webpack build 和 browser 這三大塊。
剩下的那幾部分就是 SSR 須要額外新加的部分,一個個來看。
Server entry & client entry 二者的有共同的詞尾 entry,對應的是 webpack.config 中的 entry,即打包入口文件,也就是分別表明服務器端所運行代碼的入口和瀏覽器端所運行代碼的入口文件。
入口文件天然不用多複雜。
server entry: 根據路由狀態,返回渲染完成後相應的組件
clinet entry: 將應用直接掛載到 DOM 上
OK。它倆的事就作完啦,是否是很簡單。
有了不一樣的 entry,打包的內容也有不一樣,天然就要兩套配置。
配置 webpack 的配置文件的確很麻煩,但有個好消息就是原先的打包文件不須要修改,只需加一個 server 端的配置文件就能夠了。server 端的配置文件也至關簡單,基本能夠沿用客戶端的配置,改改 entry
和 output
基本就差很少了。
不過,有一點要注意,必定要將 target
屬性設置成 node
,否則打包完了也無法在 node 環境下跑。還能夠將全部依賴都設置成 externals
(跑在服務器本地嘛,依賴天然都拿獲得),這只是個優化點,不加也沒有任何問題。
有了配置文件,也就能生成 Server Bundle 了,只剩下最後一塊 Bundle Renderer 了。
到這裏纔要用上 vue 爲支持 ssr 所依賴的庫 vue-server-renderer
。
經過 vue-server-renderer
提供的 API 就能容易地根據 url 生成對應的組件樹,而後將它返回給客戶端。
這裏要注意,由於用的是 webpack 打包後的文件,因此只能用 createBundleRenderer
而不能用 createRenderer
來建立 renderer。
建立 renderer 的時候還能夠爲它配置 cache,方法在 README 中也寫得很清楚了,因爲我我的博客的場景不適合添加 cache 就沒有添加。
這樣從 SPA 到 SSR 的變動就完成了,經過瀏覽器訪問看看是否是已經將頁面整個返回了。
遇到控制檯 ⚠️
The client-side rendered virtual DOM tree is not matching server-rendered content.
固然,多是你的標籤不對應,也有多是 text node 中的空格字符長度不對應,我我的遇到的都是空格不對應形成的問題,非常尷尬(多是使用 template 語法形成的)...
Memory-fs
在開發環境下,因爲使用服務器渲染,天然不能使用 webpack-dev-server,而是要用 webpack-dev-middleware。然而,webpack-dev-middleware 所建立的文件都是在內存裏的,server 就沒法讀到 server bundle 文件,這裏就要用到 memory-fs 來從內存中讀文件。
KOA 2
用 koa 2 做爲服務器時,在 renderToString
或 renderToStream
時,記得外面要加 await
,不然,程序就不等組件渲染好,就直接跑下個 middleware 去了。
(奉勸你們不要用 koa 做 SSR 服務器,koa 和 webpack-dev-middleware 天生水土不服,不要問我爲何~?)
document
在 Server 端渲染時,node 環境下是沒有 document 對象的。當一個界面的顯示依賴於 document 對象(好比,頁面滾動監聽事件),那麼,在 node 端運行時就會報錯。
這時,有兩個解決的辦法。
根據運行時的環境變量,經過添加邏輯來判斷是否依賴 document
使用 jsdom mock document 對象(我的偷懶的作法)
固然,從設計的角度移除對 document 的依賴就最好啦。
$root._isMounted:組件中能夠用這個參數來判斷應用是否爲第一次掛載
這樣當瀏覽器請求時,返回的頁面是服務器渲染以後的,瀏覽器解析後,頁面仍就是一個單頁應用。
最後,看效果的戳這裏,看代碼的戳這裏,原先 SPA 的代碼依舊保留在了 SPA 分支。
對 Vue SSR 有興趣的童鞋,必定要看看 vue hackernews 2.0,大神的水準比我但是高多了。
最後的最後,吐槽下 Daocloud,最近老掛我服務器,枉我一直爲它說好話。
本身寫完,看看感受好簡單,爲何還搞了那麼久...