Skip to main content

網頁載入效能

本文包含所有載入等相關優化問題,不深入原理,只是讓你知道所有東西該怎麼設定,讓你一篇就列舉完不用再去查其他來源。

打包

正確做法是 50KB 內直接全部打包即可,更大就按照功能拆分,大型 vendor JS 則做拆分 chunk。

多檔案打包在 HTTP/1.1 是必要的,因為每個檔案都要佔用一條 TCP 連線,瀏覽器限制約 6 條並行連線。HTTP/2 之後理論上打包已經不必要,可多工多個檔案並行傳輸,然而打包還是有很多好處,比如:

  1. 每個 request 都有 HTTP header
  2. 字串更多,壓縮效率更高
  3. 小檔案可能填不滿一個 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 但關鍵的 script
    • low:carousel 後續圖、非關鍵 preload、背景 API fetch
  • loading — 控制是否延遲載入
    • eager(預設):fold 以上圖片,尤其 LCP 圖絕對不能加 lazy
    • lazy: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

請見專文響應式圖片 srcset 和 sizes 設定

Preload 圖片

是否在 head 加上 <link rel="preload" as="image" href="..."> 是比較常見的錯誤因此提升到二級標題,這裡講的是 preload 圖片,因此討論的是首屏的 LCP 圖片問題,我的看法是靜態網站 preload 首屏圖片對沒什麼幫助。

首先搞懂 preload 設計目的是用於較難發現的資源(fonts included in stylesheets, background images, or resources loaded from a script),他讓瀏覽器更早發現你的資源且強制載入,但是

  1. 瀏覽器本身就有 Preload Scanner 這個機制,不等 DOM 建構就會自動判斷資源優先級並且馬上發出請求
  2. CSS/JS 本來就是最重要,最該先載入的東西
  3. 那麼再來才是你的首屏圖片
  4. 可是首屏圖片也不會離 head 多遠
  5. 那麼是否在 head 加上 preload 的延遲也就是幾百 byte 而已
  6. 實際測試就是下載開始時間差了約 40 ms

結論是可以做但影響不大,但是如果你用 CSS/JS 而不是 img 標籤載入背景圖片,這時候使用 preload 就很有用了。

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 區域裡面可以開啟此功能。

描述的不是很精準但大概是這個意思。

Footnotes

  1. AVIF 雖然瀏覽器支援度已經很高了,但是圖片下載之後用戶電腦的圖片工具可能打不開。