Nuxt 專案效能優化調研分析
效能優化,這是面試中經常會聊到的話題。我覺得效能優化應該因具體場景而異,因不同專案而異,不同的手段不同的方案並不一定適合所有專案,當然這其中不乏一些普適的方案,比如耳熟能詳的檔案壓縮,檔案快取,CDN,DNS 預解析,等等,但是我更希望聽到的是因為不同的專案不同的需求,解決不同的問題而採取的不同的優化手段,比如 BigPipe,分段輸出頁面的各個部分,對於 SNS 網站是非常合適的,減少了使用者的等待時間;相對應的還有一個 BigRender,這是一個大的延遲載入,360 導航首頁目前還在使用,京東淘寶首頁也是這個思路,對於一些類入口網站非常適用,但是如果你的網頁內容不是非常多,就沒有必要了
今天要說的是 Nuxt。Nuxt 是支援 Vue SSR 的一個框架,底層需要執行 Node 服務。大概描述一下 Vue 的渲染過程,首先每個元件都會被編譯生成一個渲染函式(這部分基本 webpack 打包已經做掉),然後渲染函式生成虛擬 dom,最後虛擬 dom 通過 patch 方法將真實 dom 渲染到頁面上。Nuxt 其實就是將這部分放到了服務端去做,在服務端拿到渲染頁面所需要的 html,從而使得 html 能夠直出,而客戶端其實還是會執行整個 Vue 的生命週期,這就帶來了一個問題,這部分操作放在了服務端其實是非常耗 cpu 的,建立元件例項和虛擬 DOM 節點的開銷,無法與純基於字串拼接的模版的效能相當,如果是不加優化的 Nuxt 專案,高併發下是很脆弱的,畢竟 Node 執行在單執行緒下,不適合 cpu 操作密集型的場景
使用 Nuxt 的專案無非看中了它的兩大優點,一是服務端渲染滿足 SEO 的需求,二是首屏直出比 SPA 快,再加上如果如果公司是 Vue 系,使用 Nuxt 就更順理成章。但是不要忘了效能,高併發下 Nuxt 效能確實不樂觀,我測試了官網的 hackernews demo 專案,2 核 cpu + 4g 記憶體,400 併發下它的吞吐量不超過 50,就算是最簡的 Nuxt 專案,吞吐量也就 300+,這就說明如果專案不做快取,300+ 已經是最大的吞吐量了,而最小 express demo 可以輕鬆到 3000,這就決定了高流量專案並不會輕易去使用 Nuxt
我們的專案目前其實是一個不加優化的 Nuxt 專案,因為使用者不多,平時並沒有什麼問題,但是一到展會,就會有不少使用者同時訪問,反饋頁面會很卡。同條件下做了壓測後,吞吐量也是 50 上下,平均響應時長七八秒,所以卡是正常現象
看了一下專案程式碼,發現了幾個問題:
專案沒做快取,所以每次訪問都會經歷所有 Nuxt 生命週期,消耗 cpu,這點是最致命的
專案打包預設 gzip。Nuxt 專案打包會預設在服務端開啟 gzip,因為我們閘道器層已經做了 gzip,所以這裡是不必要的,測試了下關掉 gzip 吞吐量和響應時間都能提高 20% 左右。具體做法是在 nuxt.config.js 中配置(還是得看 英文文件,會告訴你如何不設定 To disable compression,use compressor: false,中文文件當時三月份我寫這文的時候還沒加這個選項,而目前中文文件也沒有翻譯這一句 2020-07-16)
render: { compressor: false }
API 請求比較亂。很多請求並沒有很好地區分客戶端和服務端,而是都由服務端去做了,造成服務端壓力過大,其實多數和使用者有關的請求理應放到客戶端。有的介面為了方便,一次性返回了所有內容,也沒有做客戶端/服務端區分。另外,服務端的介面請求可以併發,用類似 Promise.all 的形式去控制
SEO。有的內容頁面,很長,有五個部分,除了內容外,還有猜你喜歡等其他部分,詢問了 SEO 同事,說這幾部分都是需要 SEO 的,我不是很懂 SEO,但是在我看來,ssr 只應該渲染首屏內容,而 UI 在設計的時候應該把主要內容設計到首屏,從而滿足 SEO
對此我覺得可以從兩個方向去優化:
快取。快取是最重要的方案,針對 Nuxt 專案可以做三級快取,頁面快取、元件快取以及 API 快取。頁面快取是最重量級的快取方案,能不能做頁面快取可以從以下兩個點判斷:
同一個 URL,對於 登入 / 非登入 使用者,服務端渲染的內容是相同的(注意是服務端渲染內容,而非前端)
同一個 URL,對於不同的登入使用者,服務端渲染的內容是相同的,即沒有一些個性化的渲染(常見的個性化渲染,比如針對不同使用者渲染不同的猜你喜歡內容等)
其實也就是返回的 html 程式碼相同就好,主要關注下返回的全域性 store 是否一致,另外也不能做一些服務端才能做的操作,比如 set-cookie 等
控制好首屏模組個數,對返回的結果進行精簡,最小化,保證吐出到瀏覽器的內容足夠小。這就是前面說的並不要對所有模組都做 ssr,需要首屏呈現的/需要爬蟲爬的,我們直出,其他部分做 CSR 就行了
而我們的網站大部分頁面是滿足做頁面快取條件的,測試了下如果做頁面快取,吞吐量能到 500+,這個資料這個時候其實是和頁面大小有關係了,頁面快取的效能是能滿足需求的。而有另一類頁面,相同的 URL 會返回不同的內容,而且整頁都是不同內容,它的實現是獲取 cookie 中的不同 city-id,渲染不同城市的內容,很顯然這部分頁面做不了頁面快取了,API 快取和元件快取理論上都是可以試試的
做快取優化,至少需要訪問一次,第二次才能生效,那麼還有另一種情況,對於這樣的路由 /store/:id,併發開啟 id 0~1000,很顯然每個頁面都是不一樣的店鋪資料,並不能命中快取(可能命中元件快取,暫時忽略),這個時候只能從 Nuxt 生命週期上去優化了,那麼以上方向的第二點,控制首屏模組個數就能用到了。所以本文一開始我就說,不同的方案是適配不同的場景的,解決不同的問題會採取不同的手段
補充知識:Nuxt實現的SSR頁面效能優化的進一步探索與實踐
前言
本文之前,先簡單介紹以下幾個概念:
SSR指服務端渲染,即頁面是通過服務端渲染生成後返回給客戶端的,SSR主要為了提高頁面載入速度,改善使用者體驗,也可用於SEO搜尋引擎優化。
Nuxt.js 官方定義: Nuxt.js 是一個基於 Vue 的通用應用框架。 通過對客戶端/服務端基礎架構的抽象組織,Nuxt.js 主要關注的是應用的 UI渲染。我們的目標是建立一個靈活的應用框架,你可以基於它初始化新專案的基礎結構程式碼,或者在已有 Node.js 專案中使用 Nuxt.js。
個人理解:Nuxt.js 就是預設了開發服務端渲染應用所需要的各種配置,使用 Webpack 和 Node.js 進行封裝的基於Vue的SSR框架。
背景
我們部門從事的都是面對使用者的業務需求開發,面對使用者,意味著對頁面的體驗要求會更高,最直觀體驗是頁面首屏的載入速度,載入速度優化是我們體驗優化的長期、重要的一部分;本文的起源正是首屏載入速度優化。
頁面載入速度優化的核心包括三點:減少資原始檔的請求數量;減小每個資原始檔的大小;提高每個資源的載入速度;
諸如合併API訪問,壓縮混淆檔案,支援webp圖片,資源cdn快取等等常用辦法,都是以上面三個核心為出發點的; 這些常用辦法基本都可以通過webpack配置,公司基礎服務,程式碼較小的變更完成。
我們負責的各主流量入口頁面,已基本做過以上常用的優化,但由於主入口頁面資源量較大的原因,優化後並不能達到預期的效果,我們需要探索其它優化方案。 我們快速想到了用SSR的方案進一步解決載入速度問題,從零開始的搭建服務端渲染應用相當複雜,肯定會涉及到服務端的開發,作為獨立的前端團隊,成本較高昂; 我們決定嘗試是否能找到一種成本較低的現有SSR框架,以達到目的;
因主入口頁面技術棧為vue,方案調研中自然而然的看到了Nuxt.js此種基於Vue的SSR框架; Nuxt.js和專案技術棧匹配度急高,學習成本極低,自然成為我們的第一選擇;
我們引入Nuxt.js,最初只是利用了服務端非同步獲取API介面資料和服務端渲染兩項功能,去重構了我們的專案,重構後效果基本達到我們的預期,正常網路狀態下,基本可以達到秒開; 入口頁面,團隊是作為一個長期的專案進行不定期優化的,我們逐步圍繞Nuxt.js框架,對專案做了進一步優化升級,本文主要介紹我們Nuxt.js頁面優化的進一步探索與實踐; 至於如何搭建初步的Nuxt專案,需要感興趣的各位自行檢視官方文件及自我實踐了,本文不做贅述。
探索與實踐
我們主要的探索與實踐可行方向主要有兩個:
一、Nuxt.js特性合理應用
應用到的特性主要包括asyncData非同步獲取資料、mounted不支援服務端渲染、no-ssr元件不在服務端渲染中呈現;
通過相關特性做到API資料和頁面結構合理拆分,首屏所需資料和結構通過服務端獲取並渲染,非首屏資料和結構通過客戶端獲取並渲染。
示例程式碼:
no-ssr結構拆分
<template> <div> <!-- 頂部banner --> <banner :banner="banner" /> <!-- 非首屏所需結構,通過no-ssr元件達到不在服務端渲染目的--> <no-ssr> <!-- 商品列表 --> <prod-list :listData="listData"/> </no-ssr> </div> </template>
API資料拆分
export default { async asyncData({ app,query }) { try { // 獲取頁面頂部輪播圖資訊 const getBanner = () => { return app.$axios.$get('zz/zy/banner') } // 獲取底部配置資訊 const getFooter = () => { return app.$axios.$get('zz/zy/footer',{ params: { smark: query.smark } }) } // 併發獲取首屏資料,服務端獲取 const [banner,footer] = await Promise.all([getBanner(),getFooter()]) return {banner: banner,footer: footer} } catch (e) { console.log('interface timeout or format error => ',e) return {} } },mounted() { // 非首屏使用的資料,客戶端獲取 this.loadListData() },methods: { loadListData() { this.$axios.$get('zz/zy/list').then(() => { // 資料處理邏輯 }) } } }
二、服務端引入快取
服務端開發意味著快取可作為效能優化的最直接法門,Nuxt.js作為一種服務端渲染框架,也不例外;針對不同的頁面,不同的資料狀態,可主要區分為下面三類快取:
1、API介面資料快取
將服務端獲取的資料,全部快取到node程序記憶體中,定時重新整理,有效期內請求都通過快取獲取API介面資料,減小資料獲取時間;
此種快取適用於快取的部分API資料,基本保持不變,變更不頻繁,與使用者個人資料無關。
示例程式碼:
import LRU from 'lru-cache' const CACHED = new LRU({ max: 100,// 快取佇列長度 maxAge: 1000 * 60 // 快取時間 }) export default { async asyncData({ app,query }) { try { let banner,footer if (CACHED.has('baseData')) { // 存在快取,使用快取資料 let data = CACHED.get('baseData') data = JSON.parse(data) banner = data.banner footer = data.footer } else { // 獲取頁面頂部輪播圖資訊 const getBanner = () => { return app.$axios.$get('zz/zy/banner') } // 獲取底部配置資訊 const getFooter = () => { return app.$axios.$get('zz/zy/footer',{ params: { smark: query.smark } }) } [banner,getFooter()]) // 將資料寫入快取 CACHED.set('baseData',JSON.stringify({ banner: banner,footer: footer})) } return {mods: mods,footer: footer} } catch (e) { console.log('interface timeout or format error => ',e) return {} } } }
2、元件級別快取
將渲染後的元件DOM結構存入快取,定時重新整理,有效期通過快取獲取元件DOM結構,減小生成DOM結構所需時間;
適用於渲染後結構不變或只有幾種變換、並不影響上下文的元件。
示例程式碼:
nuxt.config.js配置項修改
const LRU = require('lru-cache') module.exports = { render: { bundleRenderer: { cache: LRU({ max: 1000,// 快取佇列長度 maxAge: 1000 * 60 // 快取1分鐘 }) } } }
需要做快取的 vue 元件, 需增加 name 以及 serverCacheKey 欄位,以確定快取的唯一鍵值。
export default { name: 'zzZyHome',props: ['type'],serverCacheKey: props => props.type }
如果元件依賴於很多的全域性狀態,或者狀態取值非常多,快取會因頻繁被設定而導致溢位,這樣的元件做快取就沒有多大意義了;
另外元件快取,只是快取了dom結構,如created等鉤子中的程式碼邏輯並不會被快取,如果其中邏輯會影響上下邊變更,是不會再執行的,此種元件也不適合快取。
3、頁面整體快取
當整個頁面與使用者資料無關,依賴的資料基本不變的情況下,可以對整個頁面做快取,減小頁面獲取時間;
頁面整體快取前提是在使用Nuxt.js腳手架工具create-nuxt-app初始化專案時,必須選擇整合伺服器框架,如express、koa,只有這樣才具有服務端中介軟體擴充套件的功能。
示例程式碼:
服務端中介軟體middleware/page-cache.js
const LRU = require('lru-cache') let cachePage = new LRU({ max: 100,// 快取佇列長度 maxAge: 1000 * 60 // 快取1分鐘 }) export default function(req,res,next){ let url = req._parsedOriginalUrl let pathname = url.pathname // 通過路由判斷,只有首頁才進行快取 if (['/home'].indexOf(pathname) > -1) { const existsHtml = cachePage.get('homeData') if (existsHtml) { return res.end(existsHtml.html,'utf-8') } else { res.original_end = res.end // 重寫res.end res.end = function (data) { if (res.statusCode === 200) { // 設定快取 cachePage.set('homeData',{ html: data}) } // 最終返回結果 res.original_end(data,'utf-8') } } } next() }
nuxt.config.js配置項修改,引入服務端中介軟體
//針對home路由做快取 serverMiddleware: [ { path: '/home',handler: '~/middleware/page-cache.js' },]
總結
本文主要是針對Nuxt.js框架實現的頁面,效能優化方案進一步探索和實踐的總結,彙總一些思路與方向;期望各位小夥伴在其它SSR相關頁面優化過程中,能起到一定的啟發作用。
以上這篇Nuxt 專案效能優化調研分析就是小編分享給大家的全部內容了,希望能給大家一個參考,也希望大家多多支援我們。