今天 stakeholder 或 PM 希望你開始一個新的 project,過了幾個月終於上線了,大家都很開心整體速度都還行,然後大家開始繼續加 feature 結果發現整體效能越來越慢而使用者開始抱怨然後不來這個網站了,現在大部分框架工具都幫你把一些底層 low level 東西設定好了比如 webpack 但多了解他們具體做了什麼對自己工程生涯總是好的,而且下次如果被問能不能提高效能需要 debug 時候也知道應該去哪裡 debug
這邊圖片太大可以是檔案本身 file size 太大或是圖片尺寸太大,尺寸太大但給他的空間不一致使用者看起來就是覺得怪,而本身 file size 太大在不對的情境下下載速度就會慢,假如使用者用手機在瀏覽你的網站而你 by default 直接給他 4K 圖片而剛好網路不好,這會下載超久然後使用者不耐煩就不來了,除了圖片可以用 webp 格式或是比較新的 AVIF 格式來壓縮檔案,也可以多利用 picture tag 來根據使用者的 viewport 給他們相對應的圖片比如1200 px 以上給大一點漂亮一點的圖片而 768 px 以下給小一點但還是看得清楚的圖片,Steve Jobs 曾經說過在手機上 720 p 以上的畫質基本上人們已經很難看出差別了所以在手機上在預設情況下給特別漂亮特別大的圖片反而沒有用只會拖慢整體 loading 速度。
如果有聽第一集的 podcast 聽眾會知道,雖然那應該已經很久遠了,script 和 CSS 下載的先後順序會直接影響到網站如何渲染,前端學習過程中應該會有很多人說要把 script 放在 body 最下面,放在 head 裡面瀏覽器會暫停所有事情只為了下載和 parse JavaScript
標題寫 webpack 但其他比如 Vite 應該也通用,常常看到的方案有根據 page route 來做 chunk splitting 這樣確定我到這一個 route 時候只會下載跟這個 route 有關的 JavaScript,這現在很多 meta framework 都會幫你做了,有些也會根據 build 出來的 hash 來幫你加到檔名裡面和把 node_modules 的東西放在一個所謂的 common bundle,這麼做的用意都是希望可以讓瀏覽器做 caching 的動作這樣比如我今天沒有更新 node modules 或是我只更新了首頁相關的檔案使用者只需要重新下載被更新過的就好而不是整個 app 再下載一遍。
bundle split 的延伸就是可以讓你做 lazy loading 這樣那些 page 一開始 load 的時候不是很重要的東西不需要第一時間被下載下來,可以等一段時間、等一個 intereaction、等到網頁滑到那地方再下載,lazy loading 這個技巧也可以用在圖片上面,先下載對使用者現在來說不是最重要的東西就是浪費也佔用 http request 的先後順序。
你可能沒感覺但現在很多工具或是 server 都會自動幫你開壓縮比如 gzip 或是 brotli 來幫你的 JS CSS 壓縮之後你的瀏覽器會做解壓縮的動作再去 parse 這檔案是個什麼東西,大部分是可以壓到比原來小 70% 甚至更小的檔案,這邊就物理取勝嘛,東西越小絕對傳輸越快。
英文有個字叫做 jank 大概意思就是不順或是使用網站時候突然卡了一拍才有反應,通常畫面如果要保持60 fps 那系統在每一幀之間有大概 16 ms 處理你寫的 function 要不然瀏覽器會慢一拍使用者就會覺得好卡,如果 funciton 在處理複雜的 array 或是需要 massage data 就會佔用瀏覽器的 UI thread 導致 event loop 跳不到 paint step,可以用某個技巧叫 timeslicing 把要做的 operation 拆成好幾份然後用 settimeout 或是 requestanimationframe 把要做的事情在這 16 ms 做了,不過更好的作法可能是儘量讓設計上不需要做這些 massaging 或是 API response 貼近前端需要的,我先說不是每間公司都可以這麼做但我們之前在一個 project 因為也是不太喜歡我們 GraphQL schema 所以自己在 Nextjs 裡面弄了一個 tRPC 來做早期的 massaging 然後 Nextjs 會把這些 response 都 cache 下來,前端只需要做最少的事情。
每個框架都有類似的概念就是你要決定你的 state 要怎麼在每個 component 之間共用,props drilling或是利用 state management 來管理你的 state 但不要 props drilling 因為像 react 只有 props 不一樣雖然你只是把這個 prop 傳到下一個 component 但因為不一樣所以會 re-render,就像 context API 也要想一下因為就是另一種層面上的 props drilling,或是你有用 state management 然後有個 feature 需要更新東西,可以 batch 在一起可以發一次讓比如 zustand 更新就一次弄而不要分開弄,每次 state 更新就會 trigger 一次 re-render 然後重新計算。
有時候東西下載慢因為 server 反應速度慢沒辦法對你的 request 反應或是物理位置比較遠,可以開 CDN 或是比較複雜的開多個 region 讓你的 server 離使用者更近一點,有些人會把 server 架在 serverless 方案比如 Lambda function 而這類 function 有時候會因為當沒有流量時候就睡著了導致之後需要 cold start 這樣 latency 就很長了所以要注意,有些人會開一個新的 server 去叫 lambda 哈哈。
看你的 project 是什麼性質的,大部分應該或多或少都可以利用不同 layer 的 caching 和不同的 revalidation 時間來讓你的 application 變得更快也可以在某些情況下讓你的 server 不用一次處理太多 requests,這邊 caching 也包含剛才提的 hashing 和利用 header 來控制每個 asset 應該在 client 端 cache 多久。