網頁載入效能
本文包含所有載入等相關優化問題,不深入原理,只是讓你知道所有東西該怎麼設定,讓你一篇就列舉完不用再去查其他來源。
打包
正確做法是 50KB 內直接全部打包即可,更大就按照功能拆分,大型 vendor JS 則做拆分 chunk。
多檔案打包在 HTTP/1.1 是必要的,因為每個檔案都要佔用一條 TCP 連線,瀏覽器限制約 6 條並行連線。HTTP/2 之後理論上打包已經不必要,可多工多個檔案並行傳輸,然而打包還是有很多好處,比如:
- 每個 request 都有 HTTP header
- 字串更多,壓縮效率更高
- 小檔案可能填不滿一個 TLS Record 造成資源浪費
JS
現代已經不再需要為了效能把所有 JS 移動到 body 標籤結束前才載入了,async/defer/type="module" 都可以延後執行避免阻塞,defer 和 type="module" 都會讓 script 標籤等到網頁渲染結束才執行 JS,async 作用相似只是下載完成後立刻執行,而瀏覽器會同時下載多個資源,因此會看到 HTML/CSS/JS 幾乎同時下載,然後 JS 等到渲染完成後才執行。
只要是 defer/type="module" 載入的都不會被記入 PageSpeed Insights 分數,但是 JS 裡面又要求其他 JS 還是會被抱怨 dependency tree 太深。
實際測試 defer 和 type="module" 使用同一個佇列。
CSS
總建議就是把所有東西都打包成唯一一個 CSS,除非你的網站 CSS 很大超過 100 KB,不過個人網站應該不會出現這種問題。最常造成效能的問題只有在 head 以外的地方載入 CSS 導致 CSSOM 需要修改,因此效能扣分。
設定 media="print" onload="this.media='all'" 可以非同步載入樣式,雖然能上場的地方不多,但是能上場時是真的很有用,簡單有效 (Resource inlining in JavaScript frameworks)。
關鍵 CSS
關鍵 CSS 壓在 14KB 以內可以在 TCP initcwnd 第一次傳輸就直接把數據一起傳完,不需要等 ACK 回應,但是實際上很少看到有人做關鍵 CSS 拆分。
圖片
WebP 是 21 世紀的神,永遠該用他,傳統 JPG/PNG 要等到完全下載之後才能解析圖片,WebP 不只壓縮率高,更可以下載到一半就開始解析圖片,各種硬體裝置也都支援良好,其他次世代格式 APNG/JXL/AVIF 全都輸在支援度上1。
你應該在上傳圖片前就先在本地將圖片轉好檔而不是透過 Hugo 幫你轉,否則每次 CI 都在浪費資源,本地轉好也就不需要處理 Cloudflare 上 Hugo 糟糕的支援度,連設定快取都困難重重。
你可以參考我的工作流程:使用 automator 簡化日常工作流程,或是使用開源專案 FotoKilof 也能輕鬆轉檔。
屬性設定
fetchpriority— 調整瀏覽器對已知資源的下載優先級high:LCP hero image、carousel 第一張、async 但關鍵的 scriptlow:carousel 後續圖、非關鍵 preload、背景 API fetch
loading— 控制是否延遲載入eager(預設):fold 以上圖片,尤其 LCP 圖絕對不能加lazylazy:fold 以下圖片,節省初始頻寬- 非常有用,明顯有感,務必記得設定
decoding— 控制圖片解碼是否阻塞主執行緒auto(預設):多數情況夠用async:非關鍵圖片,讓解碼不卡主執行緒sync:需要立即呈現的圖片
<link rel="preload">— 讓 parser 提早發現資源- 不要濫用。preload 是強制 fetch,多加反而搶頻寬,已在 HTML 直接引用的資源不需要 preload
- 用於 JS 引入的資源、CSS background LCP image、字體、dynamic import 的依賴
- 搭配
fetchpriority="high"提升 preload 的優先級(預設 preload 不一定高)
srcset
Preload 圖片
是否在 head 加上 <link rel="preload" as="image" href="..."> 是比較常見的錯誤因此提升到二級標題,這裡講的是 preload 圖片,因此討論的是首屏的 LCP 圖片問題,我的看法是靜態網站 preload 首屏圖片對沒什麼幫助。
首先搞懂 preload 設計目的是用於較難發現的資源(fonts included in stylesheets, background images, or resources loaded from a script),他讓瀏覽器更早發現你的資源且強制載入,但是
- 瀏覽器本身就有 Preload Scanner 這個機制,不等 DOM 建構就會自動判斷資源優先級並且馬上發出請求
- CSS/JS 本來就是最重要,最該先載入的東西
- 那麼再來才是你的首屏圖片
- 可是首屏圖片也不會離 head 多遠
- 那麼是否在 head 加上 preload 的延遲也就是幾百 byte 而已
- 實際測試就是下載開始時間差了約 40 ms
結論是可以做但影響不大,但是如果你用 CSS/JS 而不是 img 標籤載入背景圖片,這時候使用 preload 就很有用了。
- Optimize resource loading with the Fetch Priority API
- Optimize Largest Contentful Paint To ensure your LCP resource starts loading as early as possible, it's critical that the resource is discoverable in the initial HTML document response by the browser's preload scanner... 後略,這個意思就是 browser 自己的 preload scanner 就已經可以偵測到 img 標籤的 LCP 了,下面要你 preload 的是因為資源藏在 CSS/JS 裡面所以才需要手動指定 preload,反過來說純 HTML 圖片不需要自己設定 preload
- How To Preload Your Largest Contentful Paint Image 說了一樣的話:如果最大內容繪製(LCP)圖片是從另一個檔案中引用的,這種方式就無法運作。
- Don't fight the browser preload scanner
- Harry Roberts - Optimising Largest Contentful Paint background-image 對 LCP 很差
Speculation Rules
告訴瀏覽器預取和預渲染:預取就是進入 viewport 就會自動請求連結的資源,預渲染就是連渲染都幫你做好了,點進連結就秒切,設定方式很簡單,把這段放到 body 標籤結束前即可:
<script type="speculationrules">
{
"prefetch": [
{
"where": { "href_matches": "/*" },
"eagerness": "eager"
}
],
"prerender": [
{
"where": { "href_matches": "/*" },
"eagerness": "moderate"
}
]
}
</script>
Web Performance 2025: The Shift from Optimization to Prediction
HTTP 103 Early Hints
這是很強大的功能,但是 SSG 網站不適用。目的在伺服器處理時讓 head 標籤先傳,這個時間差客戶端就能先下載 head 裡要求的資源,但是 SSG 全靜態根本就沒有伺服器需要處理的東西,Cloudflare dashboard 的 speed 區域裡面可以開啟此功能。
描述的不是很精準但大概是這個意思。