使用Nuxt.js改善現有項目

blank

使用Nuxt.js改善現有項目

緣由

票牛的移動站一直使用的是多頁web 應用的形式,分別使用jade、less 和es6+zepto 來分開書寫頁面、樣式及腳本,再用gulp 進行打包發布。 api 接口則是和移動端保持同步,並在nginx 上做了反向代理以避免跨域問題。 2017 年早些的時候,隨著頁面邏輯複雜度的上升,開始啟用vue作為基礎框架,並對部分老頁面進行了重構。

這套方案工作得很不錯,不過純前端渲染有其天生的問題:搜索引擎爬蟲抓不到啊。在這個問題上,業界有一些嘗試,比如Prerender.io ,這些服務的思路基本就是發現爬蟲過來的時候就把請求丟過去,他們會嘗試用PhantomJS進行渲染,再把產生的html給回來。現代主流前端框架也都提供了服務端渲染(Server Side Rendering) 的實現。

取捨上講,我個人的看法是,如果只是要滿足SEO的需求,Prerender這樣的方案是值得嘗試的,然而實際上我們還有一些別的場景需要在頁面標籤裡動刀子,比如想要配置Open Graph來使iMessage裡的鏈接帶預覽圖,就得知道蘋果的爬蟲長啥樣,響應速度上也會欠佳一些。並且SSR 也讓開發者有更多的控制權,既然項目都已經遷到Vue 上了,不如就試試看2017 年的新科技吧。

Nuxt.js是由一對法國的兄弟基於vue 2.0提供的ssr能力開發的框架,基於恰到好處的約定與配置,可以顯著的降低開發者創建服務端渲染web app的門檻。如果你是從零開始,可以按照官方推薦的做法,使用vue-cli 創建項目。我們因為是已有項目,所以做了這樣幾件事情:

初始化項目

Nuxt 默認的srcDir 為rootDir,這裡我們的根目錄已經有自己項目的內容了,於是建了個nuxt 目錄以區分對待,在nuxt.config.js 做聲明就好。 middleware 下放一些中間件,用來解析當前頁的城市訊息等,配合store 下的action 存取,使得頁面都可以訪問這塊訊息。

pages 目錄下則按照現有的url 創建對應的文件,比如演出詳情的url 是/activity/detail.html ,就創建pages/activity/detail.html.vue,然後把之前的jade、less、js 都合到這個文件中去。

改造網絡請求

服務端渲染的話,一開始頁面的數據也都是由服務端發起的了。之前對網絡請求做過一層封裝,使用自己的fetch 模塊,現在要做的事情就是在fetch 的實現中區分客戶還是服務端,切換實現就好。服務端我們使用了axios 作為請求庫,客戶端就還是保留zepto。可以通過process.server / process.browser 來進行判斷。

Nuxt 對vue 的配置做了自己的擴展,作為頁面入口的vue 文件會多出asyncData、head 等配置項,asyncData 即是我們發請求獲取數據的地方。

適配現有組件

除了頁面入口之外,其他的組件都可以在瀏覽器和服務端復用。需要改造的主要有兩處:

  1. 組件內使用到瀏覽器相關api的部分需要判斷環境,比如初始化iscroll,測量寬度等
  2. 原來組件內直接從url 上獲取query 參數的地方現在要改為由父級傳入

這裡遇到比較多的是zepto 相關,一開始逐個判斷

var$;if(process.browser){$=require('zepto')}

後來嫌麻煩,就做了個$.js,把這段邏輯放進去,使用的地方直接:

var$=require('$')

如果量比較大的話可以狸貓換太子,把原來的zepto 改為zepto-origin,再建一個zepto 做以上事情,業務代碼就可以不用動了。

配置路由

路由優化也是SEO 的一部分,雖然感覺如何優化基本上是玄學,不過好些策略又似乎確實有效果。

具體來講,我們要做的就是把 m.piaoniu.com/activity/ 這樣的鏈接變成 m.piaoniu.com/sh-dramas這樣,這也是使用SSR 方案有更大的控制權才能做的優化。

對於簡單的需求,Nuxt 支持配置如下的文件結構來支持:

pages/ --| activity/ -----| _id.vue

會生成如下配置:

router: { routes: [ { name: 'activity-id', path: '/users/:id?', component: 'pages/activity/_id.vue' } ] }

如果滿足不了需要,也可以自行在nuxt.config.js 中擴展,比如前文提到的場景我們是這麼配置的:

router : { extendRoutes ( routes , resolve ) { // ... routes . push ({ name : 'category-home' , path : '/:city-:category/:filter?' , component : resolve ( __dirname , 'nuxt/pages/activity/category-home.html.vue' ) }) // ... } }

參考: Routing - Nuxt.js

更新發布腳本

原先的發布腳本做的事情是把靜態資源構建好,然後把普通資源和html先後分別發到不同的nginx服務器上,完事兒。現在多了一部,發現目錄下存在nuxt.config.js,則執行

npm run nuxt-build

之後把項目整個目錄打個壓縮包,傳到服務器上解壓,並通過pm2 做平滑重啟。

nginx 上也要做相應的改動:先嘗試try_file,如果找不到,則轉發給node 服務進行服務端渲染,相應的錯誤頁面也由node 服務提供了。

這裡由於我們要兼容已有的頁面比如activity/detail.html,而之前發不過的文件是不刪的,這就需要在nginx 上額外對這些頁面進行配置。平穩運行一段時間之後,可以在項目和服務器上把這些文件刪除,以簡化配置。

添加監控

既然要負責服務端渲染了,那麼相應的服務質量監控也要接手起來。後端我們使用的是點評出品的CAT ,nodejs也有對應的客戶端: cat-client ,使用起來並不麻煩,倒是尋找Nuxt錯誤頁的切入點花了一些功夫。官方沒有給到推薦的做法,研究了下源碼自己對錯誤入口做了切入。

Renderer.prototype.errorMiddleware=(err,req,res,next)=>{Cat.logError(req.url,err)//給用戶展示錯誤頁面,自己則可以更方便的看到具體錯誤訊息if(req.cookies.ERROR_VISIBLE){res.end(err.stack)}else{res.status(500).render('error',{code:500,error:'出錯了'})}}

另外,利用CAT 提供的Transaction 功能,也可以方便的看到服務的性能表現如何,瓶頸在哪裡,看起來長這樣:

blank

嗯,返回一個演出詳情耗時160 毫秒,還不錯。

What do you think?

Written by marketer

blank

VIDE支持微信和支付寶小程序開發

blank

淺析redux-saga實現原理