網站效能優化實錄
這篇文章主是想把近幾個月與團隊一起調教參數、設定,逐步優化專案的過程紀錄下來。
本文中提到的專案是透過 Nuxt 建置,因此文中內容主要會圍繞在 Nuxt、Webpack 的各項參數設定,紀錄過程中使用的各種優化方法。
緣起
在 Chrome 59 的更新內容 中,新增了程式使用率面版;我注意到這個更新後,第一時間便馬上查看當時手上專案的使用率:
如上圖,使用率竟然只有驚人的 17 %;另外第一屏的載入時間也非常長,平均大約需要 3.2 秒。因為這些讓我感到訝異的數據,讓我決定要開始 Survey 該如何進行優化,好好利用工作空檔來進行整個專案的優化。
專案優化
由於是 Nuxt 專案,第一時間想到的是從 Webpack 的設定開始研究;Webpack 4 增加了 optimization 的設定,包含了許多面向的優化調教選項;當時我選擇從影響最顯著的切分程式碼開始著手。
程式碼切分
從先前的使用率面板,可以看到第一項是一隻超大包的 js 檔,但使用率只有不到 10%,這是因為先前沒有設定要進行程式碼切分,而程式在經過 Webpack 編譯時,便將 node_modules 及大部分的程式碼都打包在同一隻 js 中。缺點非常明顯,所有頁面都花時間及流量下載了幾乎全部的程式碼,而實際上可能 A 套件只在特定頁面中使用。這樣打包的方式便造成了許多無謂的浪費。
要改善其實很簡單,只要在 Webpack 4 新增的 optimization 設定中加入 splitChunks,並給定基本的參數即可:
預設的設定中,Webpack 會依照各檔案之間的引用關係,把被一起重複使用的部分拆分成獨立的 chunk,同時控制單一檔案最多只會引入 3 個 chunk,避免瀏覽器 request 數量被 chunk 被占滿。
除了 Webpack 的設定外,在 Vue Router 中也可以透過非同步載入的語法,讓 Webpack 將各頁面的內容單獨切分:
來看看這部分的結果吧:
上圖是透過 Webpack Bundle Analyzer 把檔案大小視覺化出來的結果,可以看的出來經過 Webpack 打包後的程式,已經被自動分組成幾隻不同的檔案了。左上角很大一塊的橘色方塊是網頁 3D 需要用到的相關套件,淺綠色則是 Nuxt 的相關模組,中邊淺灰色的部分是 Vue 的相關模組,右邊一堆小小的淺藍色方塊則是Vue Router 設定成非同步載入的各個頁面。
這樣就能初步讓各頁面避免載入無用的程式了!
Javascript Uglify
減少了無用的部分,再來是減少有用部分的載入的大小;Webpack optimization 中可以設定 minimizer,這部份就交給 UglifyJS Webpack Plugin 來處理吧:
設定內容很簡單,使用快取、平行處理、移除註解,就不多做說明了。
HTML & CSS Minify
Javascript 最小化了,再來是 HTML & CSS 的最小化。 Nuxt 中已經整合了許多常用的 Webpack plugin,只要簡單的加上設定即可;首先是 HTML:
內容包含移除註解、移除空的屬性、移除無效的屬性、收合布林值的屬性等等,就是能省則省。
CSS 的部分,則是要透過 postcss 的設定。cssnano 是個常見的解決方案:
因為這邊還有設定 auto-prefixer,必須要額外指定 order,讓 cssnano 固定最後執行。
當時專案使用了 Element-UI,並且有整包引入再增修客製化內容,CSS 佔據專案大小占比蠻大一塊的;在 CSS minify 設定完成後,編譯出的檔案大小馬上有可觀的下降。(但 Build 的時間就明顯變長了...)
更換套件
做到這邊,已經把基本能節省的地方都處理完了;如果還想要更進一步的優化,就必須往移除 / 更換套件的方向走了。
第一個目標是知名的時間處理套件 — Moment.js;先前版本的 Moment.js 預設會載入各地的 Locale 檔案,一整組的大小來到 200+ kB,相當的龐大;因此我們選擇更換到號稱大小只有 2kB 的 Day.js。
在 Day.js 刻意的設計下,兩者的 API 設計非常相似,且專案內沒有太複雜的時間計算,幾乎是全選取代,無痛更換了;但如果讀者有遇到類似需求,在替換時還是需要特別留心,兩者 API 只是相似,並沒有完全一樣喔!
這樣一換完,又省去了 200+ kB
後端
前端優化到一個段落,載入時間大概降低了一秒半,接下來就換後端啦。
我們的專案是部屬在 AWS 上,大致上的架構可以參考前面的簡略架構圖。
在持續的追蹤、研究和討論後,我們發現當 Nuxt 要生成第一畫面的結構,向後端 API 要資料時,會先向我們設定的 Route 53 取得後端伺服器的外網 IP 位置,再對它送出 request;但其實這一段是可以走內部網路的。
request 繞出去再繞回來,一來一往也浪費了不少時間,透過設定好內網 domain,並在程式參數檔中做相對應的調整,SSR 第一畫面的回應時間又再次大幅下降。
其他
除了前述比較工程的部分外,也有把一些外部的 CSS 直接引入專案中,省去了一次來回的 request 時間,也讓第一畫面的樣式能夠直接正確顯示,不會再有畫面跳動的狀況。
其他還有一些優化的方向就與程式比較無關了,主要是把過大的圖檔、行銷素材等做壓縮最佳化,並提醒企劃 & 設計未來在處理行銷素材時要注意圖檔大小。優化網站除了程式方面的調教之外,優化圖檔對載入時間的影響也是非常巨大的喔!
優化結果
整理了前後對比,結果如下圖:
首頁: Landing page
活動列表頁:後端 API 回應資料組出列表
虛擬展覽頁:後端 API 回應資料 & 3D 互動
整體來說,算是非常成功的優化吧;第一畫面的平均回應時間減少了差不多 2.5 秒,下載內容總量的減少也很有感,在一般頁面的減少較多也很符合預期:主要是因為一般頁面不用再下載處理網頁 3D 需要的龐大套件。
附上一個 Google Audits 的結果,做個紀念 XD:
結論
江湖傳言,工程師有三大美德:懶惰、急躁、傲慢,身為一個網站工程師,每每瀏覽到效能表現貧弱的網站,總是令人感到急躁不耐;特別當發生在自家產品上時,就更是如此了。這次的優化經驗中,由畫面顯示到伺服器路由設定,跨越了蠻大的範疇,過程中也有許多眉眉角角,所幸最後的結果比預期還好,也得到一次成功的優化經驗!
那麼這篇實錄就寫到這啦,如果讀者您也有相關的優化經驗,都歡迎您在下面一起回應討論;若文中有任何不清楚或錯誤的地方,也請您不吝告知。