實現基於 Nuxt.js 的 SSR 應用

SEO

很重要,因此要普及。css

SEO: 搜索引擎優化(Search Engine Optimization),它是指經過站內優化,如:網站結構調整、網站內容建設、網站代碼優化以及站外優化等方法,來進行搜索引擎優化。html

簡單說: 經過各類技術(手段)來確保,你的Web內容被搜素引擎最大化收錄,最大化提升權重,帶來更多流量。前端

**常見關鍵詞:**白帽、黑帽、SEM、Backlink、Linkbait、PageRank、Keyword Stuffing...,總之都圍繞着一個核心:SEO;流量是變現的快車道,SEO 是低成本獲取流量的最佳方法。vue

目前大部分的搜索引擎僅能抓取URI直接輸出的數據資源,對於 Ajax 類的異步請求的數據沒法抓取;Google 除外,Google 有本身的Google’s Webmaster AJAX Crawling Guidelines.技術支持。node

SPA

**SPA:**單頁 Web 應用(single page web application,SPA),就是隻有一張 Web 頁面的應用,是加載單個 HTML 頁面並在用戶與應用程序交互時動態更新該頁面的 Web 應用程序。webpack

簡單說: Web 再也不是一張張頁面,而是一個總體的應用,一個由路由系統、數據系統、頁面(組件)系統...組成的應用程序,其中路由系統是非必須的。git

大部分的 Vue 項目,本質是 SPA 應用,Angular.js、Angular、Vue、React...還有最先的"Pjax"均如此。github

SPA 時代,主要是在Web端使用了historyhash(主要是爲了低版本瀏覽器的兼容)API,在首次請求經服務端路由輸出整個應用程序後,接下來的路由都由前端掌控了,前端經過路由做爲中心樞紐控制一系列頁面(組件)的渲染加載和數據交互。web

而上面所述的各種框架則是將以:路由、數據、視圖爲基本結構進行的規範化的封裝。ajax

最先的 SPA 應用,由 Gmail、Google Docs、Twitter 等大廠產品實踐佈道,普遍用於對SEO要求不高的場景中。

SSR

SSR: 服務端渲染(Server Side Render),即:網頁是經過服務端渲染生成後輸出給客戶端。

在 SPA 以前的時代,咱們的Web架構大都是 SSR,如:Wordpress(PHP)、JSP技術、JavaWeb...或者 DEDECMS、Discuz! 等這些程序都是傳統典型的 SSR 架構, 即:服務端取出數據和模板組合生成 html 輸出給前端,前端發生請求時,從新向服務端請求 html 資源,路由也由服務端來控制。

其次,有個概念叫預渲染(Prerendering)。

若是你只是用服務端渲染來改善一個少數的營銷頁面(如 首頁,關於,聯繫 等等)的 SEO,那你能夠用預渲染來實現。 預渲染不像服務器渲染那樣即時編譯 HTML,它只在構建時爲了特定的路由生成特定的幾個靜態頁面,等於咱們能夠經過 Webpack 插件將一些特定頁面組件 build 時就編譯爲 html 文件,直接以靜態資源的形式輸出給搜索引擎。

但實際的商業應用中,大部分時候咱們須要的是即時渲染,這也是咱們今天討論的主題。

Why

爲何要SSR,爲了體驗,還有SEO

首先,用戶可能在網絡比較慢的狀況下從遠處訪問網站 - 或者經過比較差的帶寬。 這些狀況下,儘可能減小頁面請求數量,來保證用戶儘快看到基本的內容。 能夠用Webpack的代碼拆分避免強制用戶下載整個單頁面應用,可是,這樣也遠沒有下載個單獨的預先渲染過的 HTML 文件性能高。

對於世界上的一些地區人,可能只能用1998年產的電腦訪問互聯網的方式使用計算機。 而 Vue 只能運行在 IE9 以上的瀏覽器,你可能也想爲那些老式瀏覽器提供基礎內容 - 或者是在命令行中使用 Lynx 的時髦的黑客。

在大部分的商業應用中,咱們有 SEO 的需求,咱們須要搜索引擎更多地抓取到咱們的內容,更詳細地認識到咱們的網頁結構,而不是僅對首頁或特定靜態頁進行索引,這是 SSR 最重要的意義。

簡單說就是,咱們須要搜素引擎看到這樣的代碼:

而不是這樣的代碼:

且,咱們還須要在 SSR 的基礎上實現 SPA,即:首屏渲染

基本流程是:

在瀏覽器第一次訪問某個 URI 資源的時候(首屏),Web 服務器根據路由拿到對應數據渲染並輸出,且輸出的數據中包含兩部分:

  • 路由頁對應的頁面及已渲染好的數據
  • 完整的SPA程序代碼

在客戶端首屏渲染完成以後,此時咱們看到的其實已是一個和以前的 SPA 相差無幾的應用程序了,接下來咱們進行的任何操做都只是客戶端的應用進行交互, 頁面/組件由Web端渲染,路由也由瀏覽器控制,用戶只須要和當前瀏覽器內的應用打交道就能夠了。

以前在各大 SPA 框架還未正式官方支持 SSR 時,有一些第三方的解決方案,如:prerender.io, 它們作的事情就是創建HTTP一箇中間層,在判斷到訪問來源是蜘蛛時,輸出已緩存好的html數據,此數據若不存在,則調用第三方服務對 html 進行緩存,往復進行。

另外一方法是自行構建蜘蛛渲染邏輯,當識別 UA 爲搜索引擎時,拿服務端已準備好的模板和數據進行渲染輸出 html 數據,反之,則輸出 SPA 應用代碼;

我當時也考慮過此方法,但有不少弊端,如:

  • 須要針對蜘蛛編寫一套獨立的渲染模板,由於大部分狀況下 SPA 的代碼是無法直接在服務端使用的
  • 搜索引擎若檢測到蜘蛛抓取數據和真實訪問數據不一致,會作降權懲罰,也就意味着渲染模板還必須和SPA預期輸出如出一轍

因此,最好的方法是 SPA 能和服務端使用同一套模板,且使用同一個服務端邏輯分支,再簡單說:最好 Vue、Ng2... 能直接在服務端跑起來

因而,陸續誕生了基於 React 的Next.js、基於 Vue 的Nuxt.js、Ng2 誕生之日便支持。

沒錯,Nuxt.js 就是今天的主角。

Nuxt.js

官方是這麼介紹本身的:

Nuxt.js 是一個基於 Vue 的通用應用框架。

經過對客戶端/服務端基礎架構的抽象組織,Nuxt.js 主要關注的是應用的 UI渲染。

咱們的目標是建立一個靈活的應用框架,你能夠基於它初始化新項目的基礎結構代碼,或者在已有 Node.js 項目中使用 Nuxt.js。

Nuxt.js 預設了利用 Vue 開發服務端渲染的應用所須要的各類配置。

除此以外,咱們還提供了一種命令叫:nuxt generate,爲基於 Vue 的應用提供生成對應的靜態站點的功能。

咱們相信這個命令所提供的功能,是向開發集成各類微服務(miscroservices)的 Web 應用邁開的新一步。

做爲框架,Nuxt.js 爲 客戶端/服務端 這種典型的應用架構模式提供了許多有用的特性,例如異步數據加載、中間件支持、佈局支持等。

太囉嗦了,用個人話說:

Nuxt.js是使用 Webpack 和 Node.js 進行封裝的基於Vue的SSR框架,使用它,你能夠不須要本身搭建一套 SSR 程序,而是經過其約定好的文件結構和API就能夠實現一個首屏渲染的 Web 應用。

之因此叫 Nuxt.js 也是由於受到了 Next.js 的啓發。

做者是法國的兄弟倆,EvenYou 在微博屢次提到,也在歐洲見過哥倆。

在此以前,國內有一些對 Vue SSR 的整合嘗試,但都沒有成功,主要在於 Webpack 和 Node 的結合上沒有實踐出最佳方案, 當我看到 Nuxt.js 以約束文件夾和配置文件nuxt.config.js的方式來管理多個程序組件之間的關係時,就以爲,很酷!

接下來,我不會提供具體更多的學習資料,由於官方文檔已經很是全面和成熟,已經 0.10.5 了(如今是 RC-11),只講下其架構和原理,和一些生產環境會遇到的問題。

首先,Nuxt.js 是一個 Node 程序,就像上面說的,咱們是要把 Vue 跑在服務端,因此必須使用 Node 環境。

咱們對 Nuxt.js 應用的訪問,其實是在訪問這個 Node.js 程序的路由,程序輸出首屏渲染內容 + 用以從新渲染的 SPA 的腳本代碼,而路由是由 Nuxt.js 約定好的 pages 文件夾生成的。

因此,總體上,Nuxt.js 經過各個文件夾和配置文件的約束來管理咱們的程序,而又不失擴展性,其有本身的插件機制


按照目前的版本,Nuxt.js 的程序的文件結構大概分爲如下部分:

  • pages:各頁面組件,用於生成對應路由,支持嵌套,支持動態路由
  • components:各組件,用於你本身管理公共組件或非公共組件
  • layouts:宿主佈局頁面模板組件,用於你能夠把不一樣的頁面指定使用不一樣的佈局
  • assets:用於 Webpack 編譯的各種資源,一般是一些小的資源,如代替雪碧圖之類的圖片等東西
  • middleware:中間件,首屏渲染和路由跳轉前均執行對應中間件,能夠返回promise或直接next(很實用!)
  • plugins:插件,SPA中用的各種第三方組件和一些node模塊均可以在這引入,甚至能夠引入本身編寫的第三方庫
  • store:內置了vuex,能夠直接返回數據模塊或返回一個自建vuex根對象,具體要翻文檔
  • 其餘:你能夠自定義文件夾和別名映射,文檔都有說起,這裏有配置代碼

nuxt.config.js對程序的擴展管理可大概分爲如下類:

  • build:主要對應 Webpack 中的各配置項,能夠對默認的 Webpack 配置進行擴展,如這裏代碼
  • cache:主要對應內置的組件緩存模塊lru-cache的配置對象,有默認值,可選關閉
  • css:對應咱們在SPA隨處引用樣式文件的require語句
  • dev:用於自定義配置環境變量,對應以前webpack.config.js相關文件中的變量語句
  • env:同上息息相關
  • generate:對generate命令執行時的行爲作一些定製
  • head:對應vue-meta插件的全局配置,vue-meta用於VUE/SSR程序的文檔元信息的管理
  • loading:用於定製化Nuxt.js內置的進度條組件
  • performance:用於配置Node.js服務器性能上的配置
  • plugins:用於管理和應用對應plugins文件夾中的插件
  • rootdir:用於設置 Nuxt.js 應用的根目錄(這倆api有很大合併的意義)
  • srcdir:用於設置 Nuxt.js 應用的源碼目錄(這倆api有很大合併的意義)
  • router:用於對vue-router的擴展和定製,其中還包括了中間件的配置,但並不完美(後面說)
  • transition:用於定製Nuxt.js內置的頁面切換過渡效果的默認屬性值
  • watchers:用於定製Nuxt.js內置的文件監聽模塊chokidar和 Webpack 的相關配置項

generate

同時,Nuxt.js 支持以generate命令將程序直接構建爲靜態 html ,就像上面說的,能夠做爲靜態資源直接輸出。

生產環境實踐

特殊的異步需求

這是生產環境最多見的問題,沒有之一。

個人博客右側 Sidebar 爲例,在組件結構中,其屬於宿主 layout 下的子組件,不屬於頁面組件,沒法使用頁面組件中的fetch方法, 官方的解釋是子組件沒法使用阻塞異步請求,即:子組件獲得的異步數據沒法用於服務端渲染,這對於程序是合理的,避免異常阻塞,簡化業務模型;

但實際需求中,我須要這些異步數據加強站內內鏈 SEO;因而,咱們能夠巧妙地使用內置 vuex 中的nuxtServerInit這個 API,這個 API 是在 Nuxt.js 程序實例化以後第一次執行的方法, 其內部返回一個promise,咱們能夠在這裏完成咱們站內的全部子組件異步請求,隨後將數據映射至對應子組件便可,這裏有實踐代碼

內存問題

在阿里雲低配機上出現內存膨脹的問題,一個 Blog 程序 Run 起的內存高達 100M+,固然也因爲 Node.js 的特殊單線程異步機制,暫不關心。

但在通過一段時間的訪問以後,特別是瞬間高併發訪問,會致使內存膨脹爆表宕機,經分析,是因爲組件緩存引發的,將組件緩存減小至10,問題有所改觀,但不明顯;

更深緣由是,每次用戶訪問,程序均會從新渲染組件輸出,組件數據即在一段時間內駐存在內存中,直到 V8GC 回收。

最終的解決方案是:

使用官方推薦的"使用編碼中的 Nuxt.js "方法,自定義Node.js程序的入口,對程序進行一些優化; 若是你對業務和程序都須要有深度掌控的話,我很推薦此方法,它可使你以管理 Node.js 程序的方式管理應用。

具體的優化方法是使用了一個叫idle-gc的垃圾回收模塊來優化內存管理,

idle-gc是在node早期版本中被廢除的功能,主要負責空閒時的堆內存回收,而後早期被認爲有 BUG,常常會致使 CPU 滿載,因而從 Node.js 中移除了,此項目做者修復了這個 BUG,併發布了模塊。

另外,若是機器配置足夠,建議開啓緩存,即cache選項,且適當往大的配置,cache 的意義在於使用內存常駐來減輕 CPU 的計算壓力,這對於單線程的 Node.js 是很好的業務實踐。

最新更新:已再也不使用此模塊,最終靠 [ 優化業務邏輯 ] + [ 優化頁面結構和抽象粒度 ] + [ 升級硬件 ] 來解決了問題。

這是 PM2 監控進程的平常數據之一:

移動版本適配問題

幾乎全部的搜索引擎對於 PC 和移動端業務都是分開的,因此咱們能夠巧妙地使用layouts佈局模塊來實現咱們移動端和 PC 端業務的分離; 在個人博客項目裏,因爲業務邏輯和頁面均不夠複雜,故使用了 CSS3 媒體查詢 + 組件內判斷的形式實現了移動端的適配。

Route自定義meta問題

目前 API 中對 router 的支持不夠全面,如自定義的配置都還沒法實現,不過能夠經過宿主組件對應週期的hook來實現對實例化後的 router 對象進行修改和管理,儘管這不夠優雅。

Window問題

因爲 Vue 的底層使用 Virtual DOM,因此 Nuxt.js 在 Node.js 環境中的編譯其實是對象計算爲字符串的過程,並無依賴 Window/Dom,或者說任何基於 Vue 的 SSR 程序均如此。

咱們在實際生產時可能用到一些須要依賴 DOM 的插件/擴展,正確的方法是根據官方文檔 - 只在瀏覽器裏使用的插件推薦的方法,經過變量判斷插件/擴展的應用環境, 這裏有實踐代碼,或者使用SSR版本的組件,如:vue-awesome-swiper, 或自行封裝directive類型的插件,而非component, 切記不要使用jsdom等相似 Node.js 中的 DOM 庫,這類庫自己是爲爬蟲或測試誕生的,且自己會佔據大量的內存,這不是真正的解決方案!

有關更多經常使用使用問題,能夠參考官方解答

最後:這是個人博客,也是一個完整的 Nuxt.js 程序,源碼在這裏

如有差池,期待指正

相關文章
相關標籤/搜索