Skip to content

瀏覽器依照資源使用時機,權衡連線準備、未來資源預取、當前關鍵資源預載與下一頁預渲染

優化資源的載入時機 ​

瀏覽器原本就會透過 HTML parser 與 preload scanner 提前發現 stylesheet、script、圖片等資源,但不是所有資源都能在 HTML 裡直接看見。像是 CSS background-image、@import 或 JavaScript 執行後才插入的內容,都可能比較晚才被發現。

Resource Hints 就是開發者提供給瀏覽器的提示,讓瀏覽器有機會提早解析網域、建立連線或取得資源。不過在做優化之前要先知道一件事:Resource Hints 不是免費的效能加速,而是用額外的網路請求、連線或解析成本,換取使用者稍後更快看到或使用資源。因此只有在資源很可能即將被用到時,提前處理才有正向收益!不然就是白白浪費網路資源而已。

那在每次選擇 hint 時,可以先問問自己以下三個問題:

  • 資源是當前頁面首次渲染必要,還是下一步才可能使用?
  • 使用者接下來用到該資源的機率有多高?
  • 提前下載、建立連線或執行資源的成本是否值得?

想清楚這三個問題後就可以比較清楚知道自己該如何選擇使用哪種方式,以及該不該使用 Resource Hints!

這篇會把常見的 Proactive Resource Loading 依照「提前準備的是連線、未來 subresource,還是目前頁面的資源」分成三類。至於針對下一個 document 的 prefetch 與 prerender,會留到 Speculation Rules 再細講,避免把頁面 Navigation 和單一 subresource 混在一起。

Connection ​

Connection 類型只會先處理來源的連線準備,不會直接下載頁面或其他資源。這類方式適合用在頁面很快就會請求的外部來源,但還是要留意提前建立連線會消耗資源。

DNS Prefetch ​

dns-prefetch 只會提前完成 DNS lookup,讓瀏覽器先取得網域對應的 IP 位址;它不會建立 TCP/TLS 連線,也不會下載來源上的資源。因為成本相對低,當頁面可能會用到某個第三方來源、但還不能確定是否需要完整連線時,可以先考慮這種方式。

html
<link rel="dns-prefetch" href="https://cdn.example.com">

不過目前手邊沒有合適的產品情境可以實際套用 dns-prefetch,也無法比較導入前後的差異。如果想進一步了解,可以參考 summer 大大之前寫的 DNS Prefetching:預先做 DNS 解析,幫助網頁載入速度更快;裡面的說明相當完整,這邊就先不展開細聊!

Preconnect ​

preconnect 適合用在頁面很快就會請求、而且對體驗有影響的第三方來源,先準備 DNS 與連線握手(例如 TCP 與 TLS)。因為它會消耗連線相關資源,所以一般不會對所有外部網域進行使用;對不重要或很晚才會用到的來源,提前連線反而可能浪費資源。

以下情境就很有可能適合使用 preconnect:

  • Google Fonts 會從其他網域取得字型檔,而且文字很快就要顯示。
  • 畫面的 Hero 圖片會從圖片 CDN 取得,而且圖片是主要內容的一部分。
  • SPA 首次載入後,幾乎一定會立即向跨網域 API 發出請求。
  • 付款、登入或地圖服務會在頁面載入後立刻使用。

如果第三方功能只有在使用者觸發特定條件後才會載入,就應等條件成立後再建立連線,避免過早消耗資源!preconnect 只負責提前建立連線,資源本身仍要透過後續請求取得。

以下是基本寫法:

html
<link rel="preconnect" href="https://cdn.example.com">

Future Subresource ​

如果 subresource 不是目前頁面急著使用,而是使用者接下來很可能會用到,就可以放在 Future Subresource。這類方式通常會以較低優先度處理,避免影響目前頁面的關鍵資源。

Prefetch ​

prefetch 適合提前請求目前頁面不急著使用、但使用者接下來很可能會用到的 subresource。它通常會以低於當前頁面關鍵資源的優先度處理,讓瀏覽器在有餘裕時再進行準備。

使用情境就像是當使用者正在閱讀文章列表頁,而流程上可能把他帶往某一篇文章或下一個步驟時,可以考慮預先請求下一頁可能需要的資源。這些資源不應因為提前請求而搶走當前頁面 CSS、JavaScript 或主要內容的頻寬,但如果使用者最後沒有前往下一頁,提前請求的資料就可能變成浪費;因此 prefetch 適合用在有一定使用機率、但不影響當前體驗的資源!

例如,可以在目前頁面預先請求下一篇文章可能需要的 CSS:

html
<link rel="prefetch" href="/articles/next-page.css" as="style">

Current Navigation Resource ​

如果資源是目前頁面很快就會用到,或是會影響初次渲染與主要內容顯示,就屬於 Current Navigation Resource。這類方式需要更精準,因為預載錯誤會直接和當前頁面的關鍵 request 競爭。

Preload ​

preload 適合用來提前取得目前頁面確定會用到,而且缺少它可能延後初次渲染或主要內容顯示的關鍵資源。它特別適合用在目前頁面很快會用到、但瀏覽器比較晚才發現的關鍵資源,例如 CSS @import 或 background-image 參照的圖片,以及稍後才由 JavaScript 解析後才請求的 script 或資料。它主要影響當前頁面,不是預測使用者下一頁會去哪裡。

preload 不是加得越多越好;如果預載的資源最後沒有使用,或大量預載非關鍵資源,就可能跟真正關鍵的資源競爭頻寬。

在使用 preload 時,通常也會搭配 as 屬性,讓瀏覽器知道這項資源的目的類型。它做的不只是標註名稱,瀏覽器還可以依照這個目的套用相應的請求標頭、CSP、快取比對與優先度;如果 as 寫錯,也可能讓後續請求無法重用已經預載的資源!

以下是寫法上大概呈現的樣子:

html
<head>
  <link rel="preload" href="/hero.webp" as="image">
  <link rel="preload" href="/critical.css" as="style">
  <link rel="stylesheet" href="/critical.css">
</head>

Modulepreload ​

嚴格來說,modulepreload 是 <link> 的 rel 屬性值,專門用來提前準備 JavaScript module。它會依照 module script 的載入規則,提早 fetch 指定的 module,並將處理結果放進 document 的 module map;等頁面真正遇到 <script type="module"> 或 import() 時,就能減少等待 module 下載與處理的時間。

html
<head>
  <link rel="modulepreload" href="/assets/app.js">
  <script type="module" src="/assets/app.js"></script>
</head>

它和 preload 的差異在於,preload 主要是先把資源下載下來,之後仍由瀏覽器依照實際用途處理;modulepreload 則是用 module script 的方式 fetch、parse 與 compile,讓 module 更早進入可供後續使用的狀態。瀏覽器也可能根據 module 的 dependencies 提前發出其他 request,但這是額外的最佳化行為,不能把它當成所有瀏覽器都會發生的保證。

如果頁面使用的是 ESM,而且某個入口 module 很快就會被載入,modulepreload 就可能縮短 module 的關鍵載入路徑。不過它同樣不是加越多越好;預載太多 module,反而可能和 CSS、圖片或其他關鍵資源競爭頻寬與 CPU,所以應該只挑確定很快會用到的 module。

下一個 Document 交給 Speculation Rules ​

前面提到的 <link rel="prefetch"> 適合用來準備 subresource。如果目標是下一次 document Navigation,現代 Chromium 會建議改用 Speculation Rules API 描述候選頁面,再由瀏覽器決定要進行 document prefetch 或完整 prerender。

兩者最大的差異在於準備範圍:Resource Hints 主要提示連線或單一資源,Speculation Rules 則是針對可能發生的頁面 Navigation。詳細的規則來源、eagerness 與使用成本,可以接著看如何透過 Speculation Rules 預先準備下一個頁面?。

總結 ​

Resource Hints 的核心不是把所有資源都提早處理,而是把「資源出現的時間」往前移到真正有價值的位置:

  • Connection
    • dns-prefetch:只提前完成 DNS lookup,不建立完整連線。
    • preconnect:很快會使用的第三方來源,先準備連線,但不會下載資源本身。
  • Future Subresource
    • prefetch:下一步很可能會用到,但不應影響目前頁面體驗的低優先度 subresource。
  • Current Navigation Resource
    • preload:目前頁面確定會用到,而且正常流程較晚才發現的關鍵資源。
    • modulepreload:目前頁面很快會使用的 JavaScript module,提前下載並依照 module 規則處理。

這裡還要分清楚「提早發現」和「提高優先度」:preload 會讓瀏覽器更早知道資源存在,但不代表所有情況都要把它設成最高優先度。後續談到 Fetch Priority 時,還會接著看瀏覽器如何安排不同資源,以及 fetchpriority 什麼時候才值得使用。

所以實際使用前,先確認資源是否真的會被使用,再用 Network 或 Performance 工具觀察是否縮短關鍵路徑。若 hint 沒有讓目前或下一次導覽更快,反而增加頻寬、CPU 或記憶體成本,就應該移除它。效能優化最後還是要回到實際測量,而不是看到 hint 就全部加上去!

參考資料 ​

按 Enter 或空白鍵放大圖片,按 Escape 關閉放大檢視。