SEO
很重要,所以要普及。
SEO: 搜尋引擎優化(Search Engine Optimization),它是指通過站內優化,如:網站結構調整、網站內容建設、網站程式碼優化以及站外優化等方法,來進行搜尋引擎優化。
簡單說: 通過各種技術(手段)來確保,你的Web內容被搜素引擎最大化收錄,最大化提高權重,帶來更多流量。
**常見關鍵詞:**白帽、黑帽、SEM、Backlink、Linkbait、PageRank、Keyword Stuffing...,總之都圍繞著一個核心:SEO;流量是變現的快車道,SEO 是低成本獲取流量的最佳方法。
目前大部分的搜尋引擎僅能抓取URI直接輸出的資料資源,對於 Ajax 類的非同步請求的資料無法抓取;Google 除外,Google 有自己的Google’s Webmaster AJAX Crawling Guidelines.技術支援。
SPA
**SPA:**單頁 Web 應用(single page web application,SPA),就是隻有一張 Web 頁面的應用,是載入單個 HTML 頁面並在使用者與應用程式互動時動態更新該頁面的 Web 應用程式。
簡單說: Web 不再是一張張頁面,而是一個整體的應用,一個由路由系統、資料系統、頁面(元件)系統...組成的應用程式,其中路由系統是非必須的。
大部分的 Vue 專案,本質是 SPA 應用,Angular.js、Angular、Vue、React...還有最早的"Pjax"均如此。
SPA 時代,主要是在Web端使用了history
或hash
(主要是為了低版本瀏覽器的相容)API,在首次請求經服務端路由輸出整個應用程式後,接下來的路由都由前端掌控了,前端通過路由作為中心樞紐控制一系列頁面(元件)的渲染載入和資料互動。
而上面所述的各類框架則是將以:路由、資料、檢視為基本結構進行的規範化的封裝。
最早的 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 程式,原始碼在這裡。
若有差池,期待指正
完