Web Performance

為什麼你的網站應該送出更少請求,而不只是更小請求

小檔案並不是免費的。現代 HTTP 讓請求開銷變小,但不是變得無關緊要。

The Wux Webtools Team The Wux Webtools Team 9 分鐘閱讀 AI輔助,人類審核
A stylized browser waterfall showing many small requests simplified into fewer intentional requests.
目錄
  1. 「只要把每個檔案變小」這個舒服的迷思
  2. 請求不只是位元組
  3. 「但 HTTP/2 已經修好了」,大多沒有
  4. waterfall 才是真相所在
  5. 更小的檔案仍然重要——只是重要性並不相等
  6. 許多小檔案的隱藏成本
  7. 1. 晚發現
  8. 2. Header 開銷
  9. 3. Main-thread 中斷
  10. 4. Cache 複雜度
  11. Bundling 回來了,但要有判斷
  12. 第三方請求值得額外懷疑
  13. 實用的請求減量清單
  14. 移除
  15. 有判斷地合併
  16. 延後
  17. 正確快取
  18. 重新測量
  19. 好的狀態長什麼樣

「只要把每個檔案變小」這個舒服的迷思

多年來,網頁效能建議聽起來很簡單:壓縮一切、minify 一切、讓每個資產都更小。

這個建議大致上仍然正確。40 KB 的 script 通常比 400 KB 的 script 好。最佳化過的圖片比原始匯出檔好。Brotli、AVIF、CSS minification、tree shaking,以及 font subsetting 都很重要。

但在許多正式環境網站上,更大的問題已經不再是某個過大的檔案,而是瀏覽器在頁面感覺可用之前,必須開口要求多少東西。

一個頁面的檔案大小看起來可以很克制,卻仍然很慢,因為它送出了 120 個請求:CSS 片段、JavaScript chunks、第三方標籤、字型檔、追蹤像素、icon sprites、JSON endpoints、preloads、analytics beacons,以及 cache revalidations。每一個可能都「很小」。加在一起,就形成一條很長、很脆弱的 waterfall。

實務規則是這樣:一旦個別資產已經合理壓縮,減少請求數量通常比從每個檔案再削掉幾 KB,更能改善使用者體驗。

請求不只是位元組

一次網路請求不只是資料傳輸。它是一連串工作。

瀏覽器必須發現資源、決定何時抓取、與其他資源一起排程、送出 headers、等待伺服器、接收 headers、解析回應、通常還要解壓縮,然後再用它做某件有用的事。

那個「有用的事」可能很昂貴。JavaScript 檔案必須被解析、編譯並執行。CSS 檔案可能阻塞 rendering。字型檔可能延遲可讀文字,或造成 layout shifts。圖片可能影響 Largest Contentful Paint 元素。第三方 script 可能帶來自己的依賴鏈。

這就是為什麼即使檔案很小,請求數量仍然重要。如果一個 3 KB 的 script 會阻塞 rendering、很晚才抵達,並且在錯誤時機於 main thread 上執行,它可能比一張 30 KB 的圖片更糟。

如果你正在閱讀 Lighthouse 報告,並覺得被一堆分散的警告懲罰,請先看 request waterfall,而不是個別分數。我們在 how to read a Lighthouse report without panicking 中有實務 walkthrough,但簡短版本是:找出什麼阻塞 first render,以及什麼延遲主要內容。

「但 HTTP/2 已經修好了」,大多沒有

HTTP/2 和 HTTP/3 改變了請求的成本結構。它們引入 multiplexing、header compression,以及更好的連線行為。簡單說,瀏覽器變得更擅長在更少連線上送出多個請求。

這是真正的改善。它也終結了一些舊習慣,例如極端的 CSS sprites,以及只為了避開連線限制而建立的巨大串接 bundles。

但 HTTP/2 並沒有讓請求變成免費。

當許多資源共用一條連線時,multiplexing 會有幫助,但瀏覽器仍然必須替它們排優先順序。伺服器仍然必須回應。用戶端仍然必須處理每個回應。Congestion、packet loss、TLS negotiation、DNS lookup、cache misses,以及 main-thread pressure 仍然存在。

HTTP/3 改善了一些傳輸行為,尤其是 connection migration 和 transport layer 的 head-of-line blocking。它並沒有移除發現、排程、下載、解析與執行資源的成本。

所以現代目標不是「把所有東西 bundle 成一個巨大檔案」。而是「送出更少的關鍵請求,並讓剩下的請求都是有意圖的」。

waterfall 才是真相所在

效能問題很少會用單一指標宣告自己。它們會以形狀呈現。

打開瀏覽器 network panel,看看最初幾秒。問自己:

  • 在主要內容出現前,有多少請求開始?
  • 哪些請求阻塞 rendering?
  • 重要資源是否很晚才被發現?
  • 第三方 scripts 是否正在與第一方 CSS、字型或圖片競爭?
  • 是否有許多檔案回傳 304 responses,而不是直接從 cache 提供?
  • icons、字型或 UI 片段是否被拆成比頁面需要更多的檔案?

快速的頁面通常有一條無聊的早期 waterfall。少量關鍵資源很早抵達。非關鍵資源等待。第三方 scripts 被延後、限制或移除。瀏覽器不會被迫在能 paint 頁面之前,就先 juggling 二十個優先順序。

慢速頁面往往有一條緊張的 waterfall:許多小檔案、許多 origins,以及許多很晚才被發現的資源。

更小的檔案仍然重要——只是重要性並不相等

這不是反對壓縮或最佳化的論點。這是反對只最佳化位元組、卻忽略協調的論點。

當資源很大、阻塞 rendering,或屬於主要內容路徑時,更小的檔案最重要。例如:

  • hero image 應該有正確尺寸並妥善編碼。
  • 阻塞 rendering 的 CSS 應該精簡。
  • first interaction 所需的 JavaScript 應該最小化。
  • 字型應該 subset、壓縮,並限制在實際使用的 weights。

字型是常見例子。團隊常糾結於某個字型檔是 24 KB 還是 31 KB,同時卻送出六種 weights、兩種 styles,以及多個 families。更好的修正方式不是從一個檔案削掉 7 KB,而是送出更少字型檔。如果排版是你效能工作的其中一環,web fonts are still one of the easiest wins on most sites

圖片也遵循同樣模式。AVIF 或 WebP 可以節省有意義的位元組,但在 above the fold 送出十張裝飾圖片仍然不是好計畫。當然要選更好的格式,但也要質疑每張圖片是否真的需要被請求。關於格式決策,我們的 when AVIF beats WebP and when it does not 指南,是這項 request-count 工作的有用補充。

許多小檔案的隱藏成本

許多小請求容易造成一些問題;如果你只看總 transferred bytes,這些問題不會浮現。

1. 晚發現

瀏覽器無法請求它尚未發現的東西。CSS 檔案可以引用字型。script 可以 import 另一個 script。component 可以在 hydration 後請求 JSON。每個依賴都在鏈條中創造另一個步驟。

鏈條越深,重要工作開始得越晚。

2. Header 開銷

每個 request 和 response 都包含 headers。Header compression 會有幫助,尤其是在 HTTP/2 和 HTTP/3 上,但它不會消除開銷。Cookies 可能讓這點糟得多。如果你的網站在每個請求都送出大型 cookies,小型資產在實務上就沒那麼小。

這也是靜態資產通常應該放在不帶 cookie 的 paths 或 domains 上的原因之一,也是 cache headers 值得關注的原因。如果正式環境中的 headers 行為很奇怪,debugging redirects and HTTP headers 通常比猜測更快。

3. Main-thread 中斷

許多 JavaScript chunks 可能造成重複的解析與執行工作。即使每個 chunk 都很小,瀏覽器也可能不斷停下來評估程式碼。這可能傷害 Interaction to Next Paint,並讓頁面感覺抖動不穩。

使用者不在乎每個檔案都很小。他們在乎點選選單花了 600 毫秒。

4. Cache 複雜度

如果做得謹慎,拆分資產可以改善 caching。穩定的 vendor bundle 和會變動的 app bundle 可以是好的切分。

但過度 chunking 可能反噬。更多檔案意味著更多 cache lookups、更多 revalidation 機會、更多版本協調,以及更多不小心讓其實不需要變更的資源失效的方式。

Bundling 回來了,但要有判斷

網頁效能的第一個時代喜歡 bundling,因為瀏覽器有嚴格的連線限制。接著 HTTP/2 出現,許多團隊大幅轉向積極的 code splitting。其中有些很有用,有些則變成迷信。

合理的中間路線是 route-aware bundling。

對典型的行銷網站或內容網站來說:

  • Inline 或只載入 initial rendering 所需的 CSS。
  • 讓全域 JavaScript 保持小。
  • 避免把一定會一起載入的 tiny modules 拆成分開的 network requests。
  • 延後不是立即需要的互動功能。
  • 移除無法證明成本合理的第三方 scripts。

對應用程式來說:

  • 依 route 或主要功能切分,而不是依每個 component 切分。
  • 讓 shared dependencies 穩定且可 cache。
  • 只 preload 很快就一定會需要的資源。
  • 避免在公開頁面載入 admin、dashboard、editor 或 experiment code。
  • 衡量 interaction cost,而不只是 bundle size。

Bundling 不會自動是好的。Code splitting 也不會自動是好的。有用的問題是:這次切分是否幫助瀏覽器更快交付下一個有意義的使用者體驗?

第三方請求值得額外懷疑

第一方請求至少在你的控制之下。第三方請求通常比看起來更慢、更難預測,也更昂貴。

單一 tag manager 可能觸發 analytics、ads、heatmaps、chat widgets、A/B testing、consent tools,以及 personalization scripts。每個 vendor 都可能帶來更多請求。有些會很早執行。有些會阻塞 main thread。有些會在不經過你 release process 的情況下改變。

最佳的第三方最佳化是刪除。第二佳是延後。

加入第三方 script 前,先問:

  • 這需要在使用者看到頁面前載入嗎?
  • 這需要在每個頁面載入嗎?
  • 它可以在 consent、interaction 或 idle time 之後再載入嗎?
  • 內部是誰擁有它?
  • 哪個 metric 證明它值得付出效能成本?

這就是效能變成 governance 的地方。必須有人被允許說不。

實用的請求減量清單

從最重要的頁面開始:homepage、pricing page、product page、checkout、signup,或主要 landing pages。然後逐步檢查 waterfall。

移除

  • 刪除未使用的 JavaScript 和 CSS。
  • 移除舊 experiments、廢棄 pixels,以及重複 analytics。
  • 捨棄未使用的 font weights 和 icon libraries。
  • 在適當情況下,用 CSS 取代裝飾圖片。

有判斷地合併

  • Bundle 一定會一起載入的 tiny JavaScript modules。
  • 合併阻塞同一 render path 的小型 CSS 檔案。
  • 當 SVG sprites 或 inline SVG 能在不傷害 maintainability 的情況下降低請求數時,就用它們處理重複 icons。

延後

  • Lazy-load below-the-fold images。
  • 將非關鍵 scripts 延後到 first paint 或 user interaction 之後。
  • 只在需要時載入 comments、embeds、maps、chat,以及 video players。

正確快取

  • 對 versioned static assets 使用長效 caching。
  • 對很少變動的檔案,避免不必要的 revalidation。
  • 讓 HTML 保持新鮮,但讓 hashed assets 留在 cache 中。

重新測量

每次變更後,再次檢查 waterfall。目標不是完美分數。目標是更少的 critical requests、更早的 useful rendering,以及更少的 main-thread disruption。

好的狀態長什麼樣

健康的頁面不一定有最少可能的請求數。它有一條小而刻意設計的 critical path。

瀏覽器取得 HTML、必要 CSS、如果有的話取得主要內容圖片,可能再加上一小段 navigation 或 above-the-fold interaction 所需的 script,以及讓文字可讀所需的最小字型集合。其他一切排隊等待。

這就是一個只是被最佳化的頁面,和一個感覺很快的頁面之間的差異。

縮小檔案仍然值得做。但如果網站已經合理壓縮,下一個效能收益通常不是從某個 bundle 再省下 2 KB,而是少一個 blocking request、少一個 font file、少一個 third-party script、少一條 dependency chain。

更少請求會讓瀏覽器的工作更簡單。簡單通常比我們願意承認的更快。

常見問題

一個大型 bundle 會比許多小檔案更好嗎?
不一定。一個巨大 bundle 可能延遲所有東西,尤其是在首次載入時。許多 tiny files 可能造成 scheduling 和 execution overhead。更好的模式是把總是一起需要的資源 bundle 起來,並依 route 或主要功能切分。
HTTP/2 是否表示請求數量不再重要?
不是。HTTP/2 透過 multiplexing 和 header compression 減少部分連線開銷,但每個請求仍然有 discovery、prioritization、server、cache、parsing 和 execution 成本。
我應該 inline 所有 critical CSS 嗎?
Inline 少量真正 critical CSS 可以幫助 first render,但 inline 太多會讓 HTML 更重且更難 cache。保持最小化,並測量效果。
最容易減少請求的地方是哪裡?
字型和第三方 scripts 通常是最快的收益。許多網站送出未使用的 font weights、重複 analytics、舊 pixels、chat widgets,或不需要立即載入的 embeds。
一個頁面應該有多少請求?
沒有通用目標。小型內容頁面應該有很少的 critical requests。複雜 app 可能需要更多。專注於減少 first render 之前,以及主要 interaction path 之前的請求。

來源與進一步閱讀

  1. MDN Web Docs: HTTP caching
  2. web.dev: Optimize Largest Contentful Paint
  3. RFC 9113: HTTP/2
  4. HTTP Archive Web Almanac: Page Weight
關於作者
The Wux Webtools Team

最後更新:

繼續閱讀