前面有篇文章已經寫了一遍, 就是在 Respo 站點上有的優化方案,
https://segmentfault.com/a/11...
這種方案當中, 頁面的靜態內容是提早編譯好的, 但同時前端也作渲染,
大體的流程是html
編譯階段, 調用 SSR API 對頁面進行一次渲染前端
頁面加載到前端, HTML 先於 js 在頁面上展現出來vue
js 啓動後, 生成和 HTML 幾乎同樣的 DOM, 作一次初始化git
以後按照正常的更新步驟作界面的更新github
這個作法其實近似於單頁面技術流行以前, jQuery 加模板引擎的作法,
服務端首先會用模板引擎渲染好帶數據的頁面, 前端再動態更新,
但單純用模板引擎作渲染存在的一些侷限性:gulp
對後端依賴嚴重, 必需有一個獨立的渲染服務器segmentfault
調試不方便, 好比沒有熱替換技術, 並且與模板引擎相關後端
開發方式, 單頁面技術有完整的前端路由等等方案, 用模板引擎增長複雜度緩存
因此更好的辦法是既是前端渲染單頁面, 又能原樣拿到後端運行,
也就是最近出現頻率更高的服務端渲染, 簡稱 SSR,
然而 SSR 實現起來須要處理一些配套的技術細節, 不然麻煩挺多:服務器
數據請求用戶驗證相關的問題, 須要往兩邊轉發 Cookies
首屏可能須要不止一個甚至不一樣時發出的請求, 須要額外的抽象
服務端的性能不太好控制, 除非能很好利用緩存, 或者對性能要求不高
所以在 SSR 周邊的方案成熟以前, 我認爲提供過渡的方案比較可行,
也就是, 在編譯階段先渲染靜態內容, 而動態內容仍然採用純客戶端渲染.
這也是 Addy Osmani 因此的 App Shell 渲染的問題,
這個方案能帶來一些基礎的好處:
用戶能更快地看 App Shell, 頁面也能緩存, 於是用戶覺得頁面更快了
編譯階段渲染, 於是不存在服務器開銷, 暫時也不會涉及數據請求的麻煩
前端開發的方案更加一致, 也就是目前熟悉的單頁面應用的手法
同時相比前面最爲理想化的 SSR 的效果存在一些差距:
畢竟首屏是沒有數據的, 用戶看到的是框框, 甚至只是色塊
首屏也對應加載動畫的頁面, 這相比以前須要更額外的一些代碼來完成
對於 SEO 來講, 仍是差一些, 只有頭部和導航, 沒有具體數據
若是後面須要再填充數據以應對 SEO, 遷移方案仍然不樂觀,
從單純前端渲染, 到徹底 SSR, 這之間有很多的臺階須要邁過,
並且即使如今基於已有的方案來說, 仍是有一些不成熟的地方:
前端路由和編譯階段路由要保持一致, 甚至對於 Nginx 也要作到一致.
路由能不能徹底作到支持, 實際上是不肯定的
代碼拆分, 整個站的路由分割, 實際場景當中會更復雜
固然, 我仍是以爲基於目前大量純前端渲染的單頁面, 這是一個加分項,
只要框架層面對 SSR API 作了實現, 用不大的成本就能完成這個優化,
而後對首屏加載的效果作一點點提高.
React 和 Respo 我前面已經作了演示, 感興趣往前面翻一翻,
這邊加上前面幾天用 Vue 2 仿製的一個 Demo, 幾乎一樣的功能:
Demo http://vue-coffee-workflow.co...
Repo https://github.com/coffee-js/...
大體有一些要點, 相似的項目能夠參考:
代碼在到 Node 運行, 能夠用 Webpack 預處理, 或者想辦法直接載入
用戶可能從多個路徑訪問, 因此須要編譯出多個 HTML 文件做爲入口
路由建議使用 .html
做爲後綴, 方便 Nginx 直接命中文件
組件當中不建議直接引入資源文件, 不方便處理, 建議在 CSS 當中進行打包
最主要的因素仍是框架自己對 SSR 的支持儘可能作到簡單,
如今 React, Respo, Vue 2 的 Virtual DOM 都較好地支持 SSR 渲染了,
至於 Angular, 前面收集到的資料不夠, 我等等看別人的進展.
我用 Vue 2 試驗的例子, 直接放在了 GitHub 上, 打包方式有註明.
中間爲了加載代碼的方便, 我直接用 js 來寫 vue render 方法,
實際開發當中幾乎沒人這麼作, 可是對於 Node 環境這樣更友好,
不過大概仍是 Vue 早起那種 template 的寫法更加實在一點...
核心的渲染代碼是 gulpfile 當中的這一段, 編譯路由, 生成 HTML 文件:
https://github.com/coffee-js/...
# this is the initial address entries = [ 'index.html' 'page/a.html' 'page/b.html' ] entries.forEach (address) -> app = new Vue router: router store: store components: container: Container render: (h) -> h 'container' router.push address renderer.renderToString app, (err, appHtml) -> if err? throw err else html = template.render appHtml, store.state, settings htmlPath = path.join 'build', address console.log 'render entry:', htmlPath mkpath.sync path.dirname(htmlPath) fs.writeFileSync htmlPath, html
細節不展開了, 畢竟是試驗的代碼, 實際項目並非我這樣寫的.若是有 SSR 相關的想法, 能夠一塊兒交流下, 我這邊方案還比較粗淺.