Yuanlin Lin

Blog

React 網站效能案例分析 : 六指淵 AEVFX - AE 資源特效平台

Yuanlin Lin 林沅霖

2022-05-05

昨天在 Chief Noob - Discord 有同學分享了 YouTuber 六指淵 的一個網站 - AEVFX - AE 資源特效平台。這個網站還挺有意思的,主要就是一個讓大家上傳 Adobe After Effect 特效的各種使用教學,讓需要的新手可以瀏覽的一個網站,我看完以後覺得真是一個天才的想法,說不定未來真的只要用這個網站就能把 Adobe After Effect 學會也說不定呢。

後來這個同學又補了一句:「可是這個網站跑好慢,每個特效點進去都要 Loading 很久,不知道為啥」

我仔細一看不得了了,這個網站居然是用 React 寫的,那這個效能問題就很值得探討了,畢竟學語法、學框架用法都很簡單,但如何避免錯誤的使用方式而導致效能問題卻是一個需要長時間累積經驗才能有的能力,希望透過不斷進行案例分析的方式來訓練自己的思維,同時也幫未來學習的同學整理各種需要避免的錯誤架構設計。

重現問題

我嘗試打開 AEVFX - AE 資源特效平台 的首頁,然後按了右方那個仿照 Adobe After Effect 的 UI 介面設計的特效列表,隨便選了一個特效點擊進去,然後畫面就停在了這個讀取畫面大約 2 ~ 3 秒:

一個滿有設計感的 Loading 畫面

老實說,以網站的使用者體驗來說,我覺得 2 ~ 3 秒的讀取時間還是可以接受的。但點進去以後發現特效頁面內其實只包含了:

  1. 嵌入式的 YouTube 影片
  2. 上傳影片的使用者名稱
  3. 圖文教學
  4. 按讚數量

這四個內容,那麼花了這麼多時間來載入感覺就有點奇怪了。

釐清問題

既然我們在右邊的特效選單點擊以後,需要等待一段時間的讀取,那我們自然而然就會好奇:這段讀取時間究竟是在幫我們載入什麼資料呢?一個最簡單的方式就是直接用瀏覽器的開發者工具中的 Network 頁面,找出這段讀取時間究竟在等待哪一個 Http Request 的回應。

從開發者工具可以找到載入畫面是在等哪個請求

從開發者工具可以看到,當畫面卡在 Loading 畫面的時候,我們實際上是在等待發向這個位置的一個 GET 請求,而且這個請求的 Response 大小居然是驚人的 77KB

https://aevfx.cc/_next/data/x6yBJg6Is0_glID62J8ND/effects/Plug-ins_Effects_Stylize_Cartoon_.json?effect_path=Plug-ins_Effects_Stylize_Cartoon_

如果是 Next.js 框架有一定經驗的使用者,或是嘗試理解過 React 的 SSR 框架的開發者,應該第一眼就知道這個 /_next/data 請求是什麼了。如果不知道也不要擔心,本文將用一些篇幅和你介紹這個奇妙的 API 是做什麼的。

在 SPA 中實現 SSR

note:
SPA - Single-Page Application 單一頁面應用程式
SSR - Server Side Rendering 伺服器端渲染

學習過 React, Vue 或 Angular 等現代 JavaScript 網頁框架的開發者應該都知道,基於框架開發的網站和傳統 HTML 網站的其中一個差別就是 SPA (Single-Page Application)

傳統的 HTML 網站,在點擊超連結切換頁面的時候,實際上會對新的頁面發起一個 GET 請求,然後 GET 請求會傳回一個新的 HTML 內容,瀏覽器在把他渲染到瀏覽器上,這個過程將會完全清楚當前網頁中的 JavaScript 程式運行的內容。

而 SPA 在切換頁面的時候,實際上是一個障眼法。他並沒有重新請求一個新的 HTML 頁面,而是用 JavaScript 程式直接把你現在看到的內容直接替換掉,讓你有一種切換頁面的錯覺。

圖片來源:https://themindstudios.com/blog/spa-vs-mpa/

但大家都知道,在 Next.js 框架中,我們可以用 getServerSideProps 這個功能,幫每一個路由寫一個運行在後端的函數,這個函數可以連線到資料庫獲取資料、根據 Header 進行權限管理 ... 各種需要在後端進行的工作。

function Page({ data }) { // Render data... } // This gets called on every request export async function getServerSideProps() { // Fetch data from external API const res = await fetch(`https://.../data`) const data = await res.json() // Pass data to the page via props return { props: { data } } } export default Page;

問題來了,剛剛不是說 SPA 在切換頁面的時候不會重新向後端發起請求嗎?那我切換頁面的時候,如果這個新的頁面有定義了一個 getServerSideProps 函數,這個函數不是就不會被執行了嗎?那我新頁面需要的資訊不是就拿不到了嗎?

反應快的讀者應該已經想到答案了。Next.js 就使用上面提到的這個 /_next/data 來呼叫新頁面的 getServerSideProps 函數,然後把函數結果發送回來瀏覽器,用來渲染跳轉後的新頁面!

找到問題的關鍵

現在我們已經知道,需要花很多時間等待讀取畫面是因為跳轉後的新頁面需要等待 getServerSideProps 函數回傳的結果,拿到他的結果以後才能以 Props 的形式傳入新頁面的 Component 中。

但如同剛剛上面分析的,新頁面需要的內容其實只有 YouTube 連結、圖文內容、作者資訊和按讚數之類的簡單資訊,那麼為什麼會需要等那麼久呢?回傳的結果又為什麼有驚人的 77KB 呢?最簡單的方法就是直接來看看 /_next/data 這個請求的 Response:

沒想到特效頁面除了關於特效的資訊,還有超巨量的奇怪資訊

從 Response Body 可以看到,除了關於選擇的特效的一些資訊,主要的大小是用在 effectDatatutorialsData 這個兩個欄位,他們的類型都是 Object Array,格式如下:

// effectsData [ { "folder_level_1": "Presets", "folder_level_2": "Effects", "folder_level_3": "Backgrounds", "folder_level_4": "Apparition", "folder_level_5": "", "corel_corporation": "", "gpu_acceleration": false, "keywords": "", "": "", "row": 2 }, // ... (total 848 items) ]
// tutorialsData [ { "id": "13", "type": "videos", "is_html": false, "content": "https://www.youtube.com/embed/jNgC0gr5hsg", "effect_path": "Plug-ins_Effects_Blur+&+Sharpen_Gaussian+Blur_", "likes": 30, "contact_email": "contact@sixvfx.com", "author_name": "六指淵", "row": 2 }, // ... (total 334 items) ]

從這些資料的格式,我們可以知道 effectsData 實際上就是網站右側特效列表的所有特效資料,而 tutorialsData 則是所有使用者投稿的教學影片資料。

優化方案

現在我們已經知道這個網站為什麼會很慢了,主要原因是:

  1. 前端已經有了的資料(特效列表)在每次頁面切換時,都重新下載
  2. 對於樹狀結構的資料(特效列表)用了很佔用空間的表示方式
  3. 把當前用不到的資料(所有投稿的教學影片)在每次頁面切換時,都重新下載

對於第一個問題,可以透過把「特效列表」這個資料保存在層級更高的地方,然後在第一次進入網站的時候就下載好了,所有頁面都可以直接獲取,不需要在每次頁面跳轉的時候重新用 getServerSideProps 查詢後再用 Props 的方式注入到頁面組件。

層級更高的地方是哪裡?在 Next.js 框架中,提供了一個檔案 pages/_app.js 檔案,這個檔案中的組件就是包著整個網站的上層容器!而且這個容器只會在網站剛被打開的時候加載進來,之後無論內部頁面怎麼被跳轉,他的資料和狀態都不會被影響。

參考:Advance Feature - Custom App | Next.js

同時,因為「特效列表」這個資料很龐大,也可以用瀏覽器的 localstorage 功能將其保存在前端,作為一個 Cache 的功能,這樣使用者第二次打開網頁就不需要重新花時間下載囉!

note:等等,那萬一網站之後更新了特效列表怎麼辦?使用者不就永遠只能看到舊的資料了嗎?不用擔心,我們可以讓使用者先用 Cache 的資料快速打開網站以後,在他瀏覽的同時發起請求去後端 API 下載新的特效列表,這樣就能在不犧牲最新資料的情況下優化使用者體驗了 👍

對於第二個問題,則是建議用 Nested Object 的方式來表示「特效列表」這個樹形的資料,這樣不僅可以用較少的長度表示相同的結構,在前端渲染時也可以更加高效:

[ { name: 'folder1', children: [ { name: 'folder1', children: [{ /* ... */ }] }, { name: 'folder1', children: [{ /* ... */ }] } ] }, { name: 'folder2', children: [ { name: 'folder1', children: [{ /* ... */ }] }, { name: 'folder1', children: [{ /* ... */ }] } ] } ];

對於第三個問題,則是需要調整 API 設計,只發送使用者現在用的到的教學資料即可。比如說當我打開某個特效的頁面,後端實際上只需要發送這個特效對應的教學影片列表即可!

結語

首先還是必須再次強調,六指淵願意花時間架設一個這樣的網站給所有想學習 Adobe After Effect 的人做一個學習資源的分享和傳播,我覺得是一個非常值得尊敬及學習的榜樣。我相信這不僅是需要有豐富的專業知識及經驗,更需要的是對這個領域的熱情以及樂於幫助他人的精神才能夠辦到的事情!

今天這篇文章整理了一些在 AEVFX - AE 資源特效平台 網站找到的許多可以優化效能的點,這些點其實也是許多前端開發初學者會經常踩到的坑,正如同開頭所說的,現在 Web 前端開發的門檻因為這些方便的框架而降低很多,然而,能夠正確使用這些工具並做出一個好的網站絕對不是容易的事情,希望透過這樣一篇文章可以幫助到更多正在學習 React 以及 Next.js 框架的同學們。

如果你是和我們一樣對軟體開發、產品設計有興趣的同學,歡迎加入我們的 Discord 伺服器和我們一起討論開發技術及產品設計的心得:Chief Noob - Discord

author-avatar

關於作者

Yuanlin Lin 林沅霖

台灣桃園人,目前就讀浙江大學,主修計算機科學與技術,同時兼職外包全端開發工程師,熱愛產品設計與軟體開發。

閱讀更多

回部落格首頁