服務器端渲染與Nuxt.js

從前端發展史來看服務器端渲染

前段時間在知乎上看到一篇提問,說的是爲何如今又開始流行服務器端渲染html了。整理了網上一些評論,結合本身的想法,整理出了一段前端發展史。html

早在1989年,HTML的誕生是一個物理學家爲了方便學術文檔的分享而創造,這個也是前端起始的時間。後來,CSS和Javascript加入前端行列,用來渲染頁面樣式和處理頁面動效邏輯,前端三劍客成立。剛開始的前端程序員,其實就是作切圖寫樣式(CSS)和作頁面特效(JS)等一切基礎的工做,處於程序員鄙視鏈的底層。
隨着互聯網發展與技術進步,靜態頁面已經遠不能知足產品需求,頁面上要根據邏輯產生動態的數據,這時,便迎來PHP,JSP等爲表明的web1.0時代。此時的服務器渲染,是以「文檔」爲核心思想。服務器端的邏輯是把HTML,CSS和JS當作一個靜態文件,對「文檔」而言不存在「指令」和「數據」的區別,一切都是數據。因此咱們能夠看到服務器渲染,GET就是請求一個文件,而web 1.0時代的諸多服務端框架最基礎的組件之一就是文檔模版,好比asp, JSP之類,核心設計理念就是HTML文件裏放佔位符而後由服務端邏輯替換成實際數據後一股腦返回。不少中小型項目,不分前端後端,你們都是web開發工程師,按如今的說法叫全棧工程師。而在如今來看,這樣的模式是存在不少問題的,拿jsp舉例,動態資源和靜態資源徹底耦合,服務器壓力大,並且一旦出現情況,先後臺一塊兒玩完,用戶體驗極差;jsp必需要在支持java的web服務器裏運行,性能提不上來;若是jsp中內容不少,頁面響應會很慢……
1998年,IE5.0引入XMLHttpRequest技術,實現了異步調用服務器的功能,2005年,Google在它著名的交互應用程序中使用了ajax異步通信,web前端引來2.0革命。以後W3C發佈XMLHttpRequest標準,爲以後的ajax爆發提供技術基礎。
2006年,JQuery工具庫發佈,一經出世憑藉其簡單易容的特性和解決瀏覽器兼容性的能力風靡全球。
2010年,Backbone誕生,RequireJS第一個版本發佈,前端的模塊化開發時代正式來臨了。然後,隨着前端MVC的興起,SPA(Single Page Application 單頁面應用)開始變成一種項目開發的潮流,先後端分工很是清晰。前端工做在瀏覽器端,後端工做在服務端。清晰的分工,可讓開發並行,測試數據的模擬不難,前端能夠本地開發。此時先後端分離的運動在各大公司間興起,前端自立門戶,獨立發展。前端程序員們翻身的機會來了。
然而此時,不少本不應被作成SPA的也被作成了SPA。可是,SPA應用存在種種問題,好比SEO,好比首屏加載速度,這讓前端開發人員優化愁白了頭。
隨着Node.js的興起,Javascript開始有能力運行在服務器端,這意味這有一種新的研發模式:Front-end UI layer 處理瀏覽器層的展示邏輯,Back-end UI layer 處理路由、模板、數據獲取、cookie 等。經過 Node,Web Server 層也是 JavaScript 代碼,這意味着部分代碼可先後複用,須要 SEO 的場景能夠在服務端同步渲染,因爲異步請求太多致使的性能問題也能夠經過服務端來緩解。前一種模式的不足,經過這種模式幾乎都能完美解決掉。
Web 2.0時代最大的思想革命本質不是先後端分離,而是把網頁看成獨立的應用程序(app)。先後端分離只是實現這一新架構的必然結果。對程序而言指令和數據是分離的。HTTP GET拿到的不是渲染後的網頁,而是一個由html和Javascript組成的app, 這個app以瀏覽器爲虛擬機。裝載和顯示數據是app啓動以後的運行邏輯。傳統上app叫什麼?叫Client,也就是前端。因而先後端就這麼分離了,瀏覽器變成了app的運行環境,後端蛻化成了單純的業務邏輯和數據接口。寫Javascript再也不是給網頁添特效的小伎倆,而是正經的和寫桌面應用程序同樣的工程。因而咱們看到了前端工程化,編譯(轉譯),各類MVC/MVVM框架,依賴工具,等等。前端

爲何要用服務器端渲染?

使用服務器端渲染,最主要的問題,其實就是爲了解決SEO的問題。若是SPA應用也有良好的SEO,就不用服務器端渲染這麼麻煩了。固然服務器端渲染能解決的首屏加載速度的問題也是緣由之一。那麼,SEO是什麼呢?
SEO(Search Engine Optimization),搜索引擎優化。好比谷歌、百度須要抓取你所發佈的網站信息來進行天然排序,是經過爬蟲進行的。
來看兩段代碼:
vue

上面是以前很早前寫過的兩段掘金文章的爬蟲代碼(寫的有點low),大概思路就是使用superagent發送http請求,把整個頁面(文檔對象)爬下來,包括head, body等,而後用cheerio進行解析,而後抓取頁面節點元素以及關鍵信息。可能你以爲,這個簡單,我頁面上信息都是經過ajax請求到而後插入到dom元素中的。注意,爬蟲爬到的頁面並無發送ajax請求,就是一個初始化的純靜態頁面,若是你用的是spa應用,那可能body中除了一個id爲app的節點,什麼都沒有。因此,咱們須要讓頁面在服務器端就已經被渲染完成,傳給客戶端的時候已是一個具備數據信息的靜態html文檔。固然,如今也有針對SPA應用進行的SEO優化方案,這個不在本文討論範圍以內。
如下總的列舉服務器渲染的一些優缺點:java

優勢

  • 有利於SEO。
  • 首屏加載速度快。由於SPA引用須要在首屏獲取全部資源,而服務器端渲染直接拿了成品展現出來就好了。
  • 無需佔用客戶端資源。解析模板工做交給服務器完成,對於客戶端資源佔用更少,尤爲是移動端,也能夠更省電。

缺點

  • 佔用服務器資源。服務器端完成html模板解析,若是請求較多,會對服務器形成必定的訪問壓力。而若是是前端渲染,就是把這些壓力分攤給了前端。
  • 不利於先後端分離。

VUE SSR

通用(也稱同構)的JavaScript已經成爲JavaScript社區很經常使用的一個術語。通用的JavaScript用來形容能夠在客戶端執行,也可在服務端執行的Javascript代碼。 在VUE的官方文檔上是這麼描述的:webpack

Vue.js 是構建客戶端應用程序的框架。默認狀況下,能夠在瀏覽器中輸出 Vue 組件,進行生成 DOM 和操做 DOM。然而,也能夠將同一個組件渲染爲服務器端的 HTML 字符串,將它們直接發送到瀏覽器,最後將靜態標記"混合"爲客戶端上徹底交互的應用程序。ios

前半句好理解,就是說你能夠在服務器(後端)環境中,使用vue.js來構建組件和頁面,而後將渲染好的靜態html字符串傳給客戶端展現。後半句感受句子不太通順,英文版是這樣的:git

「Finally "hydrate" the static markup into a fully interactive app on the client.」程序員

大概意思就是說,把應用傳給客戶端之後,因爲一些靜態標記,客戶端也會具有一樣的交互(就是MVVM雙向數據綁定)。github

官網給出的這張構建步驟圖也能夠看出,對於客戶端應用程序和服務器應用程序,咱們都要使用 webpack 打包 - 服務器須要「服務器 bundle」而後用於服務器端渲染(SSR),而「客戶端 bundle」會發送給瀏覽器,用於混合靜態標記。 本文不過多深刻 Vue SSR源生的內容,Vue官方推薦了一個優秀的社區項目Nuxt.js,它爲Vue的服務端渲染提供了很是良好的開發體驗,咱們將主要來討論一下它。

Nuxt.js

Nuxt.js 是什麼?

構建服務端渲染的JavaScript程序多少有些無趣,在開始編碼以前,須要大量的基礎配置。所以,解決vue.js服務端渲染問題的Nuxt.js產生了。 Nuxt.js 是一個基於 Vue.js 的通用應用框架。預設了服務器端渲染所需的各類配置,如異步數據,中間件,路由,只要遵循其中的規則就能輕鬆實現SSR。。它比如是 Angular Universal 之於 Angular, Next.js 之於 React。 經過對客戶端/服務端基礎架構的抽象組織,Nuxt.js 主要關注的是應用的 UI渲染。web

Nuxt.js 能作什麼

  • 無需再爲了路由劃分而煩惱,只須要按照對應的文件夾層級建立 .vue 文件就行
  • 無需考慮數據傳輸問題,nuxt 會在模板輸出以前異步請求數據(須要引入 axios 庫),並且對 vuex 有進一步的封裝
  • 內置了 webpack,省去了配置 webpack 的步驟,nuxt 會根據配置打包對應的文件

Nuxt.js 的安裝與運行

在安裝vue-cli的狀況下,快速生成一個nuxt項目的命令以下:

$ vue init nuxt-community/starter-template <project-name>
複製代碼

進入項目目錄後

$ npm install
複製代碼

而後啓動項目

$ npm run dev
複製代碼

這樣項目就能正常運行在http://localhost:3000

Nuxt.js 的實踐介紹

這裏就不詳細介紹nuxt.js的一些用法和API了,能夠直接看官網的教程:zh.nuxtjs.org/guide
我本身作了一個nuxt.js的簡單demo(極其簡單),Github地址,這裏就我本身的的一些體驗,對比spa應用,來聊聊這個框架。

1. Nuxt.js的目錄結構

nuxt是採用vue-cli來建立的模板,相比常規的vue模板,他們具有很是重要的一點:方便。nuxt.js一樣已經將各類項目所需的webpack配置替咱們打理好了,開箱即用,基本不須要做什麼改動。並且即便須要自定義一些配置,修改起來也很是簡單。咱們來把它的目錄結構和SPA應用做一個對比。

從上圖能夠看出,nuxt彷佛把src文件夾中的不少內容給放到了外邊,少了一些文件,多了幾個沒見過的文件夾。這些文件夾各有各的用處。

  • layouts用於放置頁面佈局。
  • middleware用於放置一些中間件,咱們在頁面組件中能夠引用這些中間件,在執行頁面邏輯的時候會先執行其中的邏輯。
  • pages就是放置咱們的全部頁面組件啦,可是,與spa應用不一樣的是,nuxt裏的page會根據文件和文件夾結構生成對應的路由,打個比方,我page文件夾下的目錄結構以下
pages/
--| _slug/
-----| comments.vue
-----| index.vue
--| users/
-----| _id.vue
--| index.vue
複製代碼

Nuxt.js 生成對應的路由配置表爲:

router: {
  routes: [
    {
      name: 'index',
      path: '/',
      component: 'pages/index.vue'
    },
    {
      name: 'users-id',
      path: '/users/:id?',
      component: 'pages/users/_id.vue'
    },
    {
      name: 'slug',
      path: '/:slug',
      component: 'pages/_slug/index.vue'
    },
    {
      name: 'slug-comments',
      path: '/:slug/comments',
      component: 'pages/_slug/comments.vue'
    }
  ]
}
複製代碼
  • plugins集中放置一些插件,好比axios等。
  • store是集中定義狀態樹。nuxt.js已經集成了vuex,這裏只須要定義一個index.js,而後對外暴露一個Vuex.Store實例便可。可是,通過踩坑,這裏的狀態樹和SPA裏有些不同,這個等會說。

2. Nuxt.js的頁面組件

從上圖的Nuxt.js和SPA的.vue單頁文件對比,,除了通常的.vue單頁文件中的常規實例屬性外(好比data, methods,props等),Nuxt.js還提供了不少方便又有意思的屬性,這也是Nuxt.js最大的特色之一。

  1. 在nuxt內部的整個執行流程中,最早通過的是狀態管理中actions中的nuxtServerInit函數,這個咱們等會再說。
  2. 而後會通過middleware裏的中間件函數,此時,尚未進行數據獲取和頁面渲染,因此咱們能夠在中間件函數中執行一些進入路由前的邏輯,好比用戶權限判斷。
  3. 以後開始獲取頁面數據,asyncData和data的結果基本相同,咱們能夠直接調用server的接口,好比理由axios發送http請求獲取頁面所需的原始數據,而後以對象的形式return出去,此時,Vue對象尚未實例化,因此asyncData裏沒法調用到this
  4. fetch裏主要用做填充狀態樹(store)數據。
  5. 這些所有作完之後,開始實例化Vue對象,這裏的邏輯和單頁應用是同樣的,在組裝好整個頁面應用以後,nuxt.js會將這個應用返回至前端。注意,這裏返回的不是單純的頁面,而是應用。此時的頁面局部spa應用的一些性質,好比數據監聽雙向綁定。
  6. 頁面來到前端後,開始執行mount的相關邏輯。
    除了應用的執行流程外,再看看頁面渲染的模塊。
  • head部分能夠自定義當前頁面的頭部信息,好比title, meta之類的。固然,若是須要定義全局head能夠在nuxt.config.js中配置。
  • layout部分能夠自定義頁面佈局,不少頁面公用的靜態頭、尾部分能夠統必定義按需引用。
  • scrollToTop用於頁面跳轉時將頁面滾動置頂。
  • transition用於頁面間跳轉的過渡動畫。

3. Vuex狀態樹

整個demo作下來,目前讓我印象最深的就是狀態樹,它和SPA應用仍是有必定區別的。
當時我須要完成的需求是,保存用戶信息,並在任何頁面可使用它,若是非登陸頁沒有獲取到用戶信息,跳轉回登陸頁
起初,個人設計思路是,在用戶登陸成功後,調用後臺接口獲取該用戶全部信息,而且存在store中。流程圖以下:

按照SPA應用的狀況,store裏的數據應該在頁面組件中都是共享的。可是,發現Nuxt中一旦頁面跳轉,整個Vuex狀態樹會重置,原來存下的用戶信息也沒有了。由此能夠推測,不一樣路由下的頁面是一個獨立的應用,它們並不會共享state中的數據。
這時我想到了本地會話存儲localStorage,只要把原流程中從store存取的邏輯改成從localStorage的邏輯便可。這種方式是可行的,可是這樣一來,Vuex感受存在感就不強了(此事必有蹊蹺),而且就無法用server層來控制會話的過時等邏輯。
後來發現,服務器端渲染的vuex中的action中提供了一個方法: nuxtServerInit。Nuxt.js 調用它的時候會將頁面的上下文對象做爲第2個參數傳給它,上下文對象能夠拿到 req請求對象,那麼就存在這麼一種邏輯。我能夠將用戶信息存儲在服務器session中,而後經過 req.session.user來訪問當前登陸的用戶。將用戶登陸信息傳給客戶端的狀態樹,代碼以下:

actions: {
  nuxtServerInit ({ commit }, { req }) {
    if (req.session.user) {
      commit('user', req.session.user)
    }
  }
}
複製代碼

這樣在配合middleware中間件,就能夠完成用戶信息獲取和會話控制,流程以下:

4. Nuxt.js的其它相關

其它還涉及的一些內容,其實看看官網教程,看看官網示例都能搞定,教程仍是很是易懂的。

總結

使用Vue,React等服務器渲染,並非走之前模板式渲染的老路。它已經跨越歷史,朝着更優秀的方面發展。 而Nuxt.js,仍是一個很是年輕的框架(如今官網纔是0.10.7版本),目前也有不少待改進的問題,但它的出現爲 Vue.js 開發者搭建服務端渲染項目提供了巨大的便利。據說Nuxt.js 2.0 即未來臨,期待版本發佈後,能給咱們帶來更多實用的新功能。

相關文章
相關標籤/搜索