紀錄工作經驗、相關知識,解決技術相關問題。

HTML, SEO, 新手 Jamp Holes, 網站優化, 網站相關

HTML Resource Hints 資源預載入教學:preload、preconnect、prefetch 與 dns-prefetch 排程機制

HTML Resource Hints 資源預載入教學:preload、preconnect、prefetch 與 dns-prefetch 排程機制 特色圖片

在上一篇「Core Web Vitals LCP 最大內容繪製優化實作:Sub-parts 四階段拆解、fetchpriority 與首屏載入調校」中,我們探討了如何將首屏橫幅改為原生 <img> 並搭配 fetchpriority="high" 消除資源載入延遲。然而,頁面上除了首屏圖片之外,還有深藏於外部樣式表內的網頁字型(Web Fonts)、存放在第三方 CDN 的腳本,以及使用者下一步即將瀏覽的頁面資源。本文依據 WHATWG HTML Living Standard 與 MDN、web.dev 文件,說明如何運用 HTML Resource Hints(dns-prefetch、preconnect、preload、modulepreload 與 prefetch)提前調度瀏覽器網路管線。


一、 預設資源載入管線的延遲瓶頸與 HTML Resource Hints 核心規範

1. 原本正常的標準寫法與瓶頸

在未配置任何資源提示的情況下,開發者通常只在 <head> 中引入外部樣式表與第三方腳本,並將 @font-face 字型宣告放在外部 .css 檔案內:

<head>
  <meta charset="UTF-8">
  <title>企業雲端控制台</title>
  <!-- 外部樣式表:內部包含 @font-face 字型宣告 -->
  <link rel="stylesheet" href="https://cdn.example.com/assets/main.css">
  <!-- 外部 API 腳本 -->
  <script src="https://api.example.com/sdk/v2/client.js" defer></script>
</head>

這種寫法會產生三個序列等待瓶頸:

1. 網頁字型(Web Fonts)深藏於外部 CSS 導致延遲發現:瀏覽器解析 HTML 時並不知道要下載哪些字型,必須先下載並解析 main.css,確認有文字用到該 font-family 後,才會發起 .woff2 請求,可能造成文字短暫隱形(FOIT)或字型切換跳動(FOUT)。

外部 CSS 內宣告 @font-face 導致字型延遲發現與 FOIT/FOUT 流程

圖:外部 CSS 內宣告 @font-face 導致字型延遲發現與 FOIT/FOUT 流程

2. 第三方跨域主機在發起請求時才進行三階段網路交握:向 cdn.example.com 或 api.example.com 請求資源前,必須先完成 DNS 解析、TCP 與 TLS 交握(MDN:HTTPS 來源為 DNS+TCP+TLS)。

第三方跨域請求在觸發時才依序執行 DNS、TCP 與 TLS 三階段交握的延遲流程

圖:第三方跨域請求在觸發時才依序執行 DNS、TCP 與 TLS 三階段交握的延遲流程

3. 透過 HTML Resource Hints 提前建立連線並平行預載關鍵資源:在 <head> 頂部加入 preconnect 與 preload,瀏覽器可及早完成跨域連線,並在 CSS 下載的同時開始下載核心字型。

透過 preconnect 與 preload 提前完成交握並平行下載關鍵字型的優化流程

圖:透過 preconnect 與 preload 提前完成交握並平行下載關鍵字型的優化流程

2. 五大 HTML Resource Hints 作用階段速查表

根據 WHATWG HTML Living Standard — Link types 規範,瀏覽器提供五種資源與連線提示指令。下表整理它們作用的目標範圍與規範用語(should 代表瀏覽器可自行斟酌,must 代表必須執行):

指令名稱(rel 值)作用目標規範強度
dns-prefetch僅對目標來源預先執行 DNS 解析提示(hint),由瀏覽器決定
preconnect預先與目標來源連線:HTTPS 來源為 DNS + TCP + TLS提示,should
preload預先下載並快取當前導覽需要的資源,優先權依 as 與 fetchpriority 決定強制,must
modulepreload預先取得 ES Module 並放入文件的 module map強制,must
prefetch預先取得並快取未來導覽可能需要的資源提示,should

下圖整理五種指令介入網路傳輸層的階段:

五大 HTML Resource Hints 於瀏覽器網路與模組管線的作用階段對照圖

圖:五大 HTML Resource Hints 於瀏覽器網路與模組管線的作用階段對照圖

各指令的標準語法與適用場景如下:

  1. dns-prefetch(預先解析 DNS):適用於稍後才會用到的第三方次要網域(如地圖服務、客服小工具),只做 DNS 解析:
    <link rel="dns-prefetch" href="https://maps.googleapis.com">
  2. preconnect(預先建立安全連線):適用於很快就會發起請求的關鍵跨網域主機(如 https://fonts.gstatic.com 或核心 API 伺服器),預先完成 DNS、TCP 與 TLS 交握;MDN 指出它對同源請求沒有效益:
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
  3. preload(預載當前頁面關鍵資源):web.dev 指出 preload 對瀏覽器是強制的(其他提示則由瀏覽器自行斟酌),適合當前頁面一定會用到、但瀏覽器較晚才發現的資源(如藏在 CSS 內的 .woff2)。應搭配 as 屬性指定資源類型,MDN 說明 as 讓瀏覽器正確快取、套用內容安全政策並設定 Accept 標頭:
    <link rel="preload" href="/fonts/noto-sans-tc-bold.woff2" as="font" type="font/woff2" crossorigin>
  4. modulepreload(預載並編譯 ES 模組):專為 ES 模組設計,MDN 說明它會取得模組腳本、解析並編譯,再存入文件的 module map。相依模組是否自動預載由瀏覽器自行決定,要確保都預載需逐一列出:
    <link rel="modulepreload" href="/js/app-core.mjs">
  5. prefetch(預取下一頁資源):提示瀏覽器使用者之後的同站導覽可能需要這個資源(如結帳頁腳本),結果存在磁碟 HTTP 快取;MDN 說明瀏覽器通常給它比 preload 更低的優先權:
    <link rel="prefetch" href="/js/checkout-step2.bundle.js">

二、 程式碼範本:字型零閃爍預載與響應式資源排程

1. 範本一:使用 preconnect 與 preload(含 crossorigin)消除網頁字型閃爍(FOIT / FOUT)

在調校網頁字型時,最常踩中的陷阱是漏寫 crossorigin 屬性。MDN 說明字型必須以匿名模式 CORS(anonymous-mode CORS)取得,且 crossorigin 要與資源的 CORS 與憑證模式一致,即使請求並非跨來源。因此,不論字型檔是放在第三方 CDN 還是同源伺服器,<link rel="preload" as="font"> 都必須加上 crossorigin 屬性;web.dev 也指出,未帶 crossorigin 預載的字型會被抓取兩次。

下圖為本節範本中同源字型的兩種情境:preload 有 crossorigin 時請求 1 次;移除後請求 2 次,Chrome 並在 Console 提示憑證模式不符。

preload 有無 crossorigin 時,字型請求次數與 Chrome Console 訊息的對照表

同源 .woff2 在 preload 有無 crossorigin 時的請求次數與 Console 訊息(Chrome、Windows 11 執行結果)

結合 preconnect、preload 與 @font-face(搭配 font-display: swap)的配置如下:

<head>
  <meta charset="UTF-8">
  <title>字型預載配置範例</title>

  <!-- 1. 若字型存放於獨立 CDN 網域,先建立 preconnect 並搭配 dns-prefetch 降級保護 -->
  <link rel="preconnect" href="https://static.example.com" crossorigin>
  <link rel="dns-prefetch" href="https://static.example.com">

  <!-- 2. 預載首屏核心標題字型:字型一律需要 crossorigin,同網域也一樣 -->
  <link
    rel="preload"
    href="https://static.example.com/fonts/inter-v18-latin-700.woff2"
    as="font"
    type="font/woff2"
    crossorigin
  >

  <style>
    /* 3. CSS @font-face 的 src URL 必須與 preload 的 href 完全一致 */
    @font-face {
      font-family: 'Inter';
      font-style: normal;
      font-weight: 700;
      font-display: swap;
      src: url('https://static.example.com/fonts/inter-v18-latin-700.woff2') format('woff2');
    }

    h1 {
      font-family: 'Inter', system-ui, -apple-system, sans-serif;
    }
  </style>
</head>

2. 範本二:結合 imagesrcset 響應式預載、modulepreload 與 prefetch 的完整標頭配置

專案同時有響應式首屏圖片、多層 ES Modules 與明確的下一步頁面時,可在 <head> 組合使用 imagesrcset、modulepreload 與 prefetch。

下圖為各類提示在當前頁面載入到進入下一頁的時間軸中觸發的階段:

當前頁面載入與跨頁導覽時間軸中各類 Resource Hints 觸發順序圖

圖:當前頁面載入與跨頁導覽時間軸中各類 Resource Hints 觸發順序圖

範本如下:

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>雲端監控儀表板</title>

  <!-- 1. 提前與後端 API 網域完成 DNS + TCP + TLS 交握 -->
  <link rel="preconnect" href="https://api.example.com" crossorigin>

  <!-- 2. 響應式預載首屏圖片:使用 imagesrcset 與 imagesizes 避免手機下載桌機大圖 -->
  <link
    rel="preload"
    as="image"
    href="/images/hero-1200.webp"
    imagesrcset="/images/hero-480.webp 480w, /images/hero-800.webp 800w, /images/hero-1200.webp 1200w"
    imagesizes="(max-width: 600px) 100vw, 1200px"
    fetchpriority="high"
  >

  <!-- 3. 平行預載並編譯 ES Module 主程式與深層相依模組,消除模組瀑布流(Waterfall) -->
  <link rel="modulepreload" href="/assets/main.mjs">
  <link rel="modulepreload" href="/assets/components/chart-engine.mjs">
  <link rel="modulepreload" href="/assets/utils/date-formatter.mjs">

  <!-- 4. 提示瀏覽器預先取得下一頁(報表匯出頁)的腳本,結果存入 HTTP 快取 -->
  <link rel="prefetch" href="/assets/pages/report-export.js">

  <!-- 正式載入模組入口 -->
  <script type="module" src="/assets/main.mjs"></script>
</head>

三、 進階排程控制:HTTP 103 Early Hints 與 preconnect 的使用限制

除了在 HTML 內寫入 <link> 標籤外,還有兩項排程原則需要留意:

  1. 伺服器端 103 Early Hints:
    MDN 說明,伺服器可在準備最終回應時先送出 103 Early Hints 與 Link 標頭,讓瀏覽器提前 preconnect 或開始 preload。MDN 建議只透過 HTTP/2 以上送出 103,多數瀏覽器也只在 HTTP/2 以上支援。
    HTTP/1.1 103 Early Hints
    Link: <https://static.example.com>; rel=preconnect; crossorigin
    Link: </css/critical.css>; rel=preload; as=style
    Link: </fonts/inter-700.woff2>; rel=preload; as=font; type="font/woff2"; crossorigin
  2. preconnect 只留給最關鍵的來源,並留意 10 秒閒置逾時:
    MDN 指出連太多第三方網域可能適得其反,preconnect 只適合最關鍵的連線;web.dev 說明瀏覽器會關閉 10 秒內未使用的連線。因此只對很快會請求的來源使用 preconnect,其餘改用 dns-prefetch;web.dev 提醒兩者要寫成獨立的 <link>,合寫會讓 Safari 取消 preconnect。

四、 HTML Resource Hints 的 5 個常見疑問

1. 疑問一:為什麼預載同源的 .woff2 字型時,沒寫 crossorigin 會導致字型被重複下載?

MDN 要求預載的 crossorigin 必須與資源的 CORS 與憑證模式一致,即使不是跨來源。沒寫時,Chrome 會判定預載與 @font-face 的憑證模式不符,預載內容無法使用,字型就多抓一次,見第二節的對照圖。

解法:as="font" 的預載一律加上 crossorigin。

2. 疑問二:為什麼 Chrome Console 會出現 was preloaded using link preload but not used within a few seconds 警告?

web.dev 指出,Chrome 對未使用的預載會在 load 事件後約 3 秒輸出此警告。例如預載了一支從未使用的腳本:

<link rel="preload" href="/unused.js" as="script">
The resource http://localhost/unused.js was preloaded using link preload but not used within a few seconds from the window's load event. Please make sure it has an appropriate `as` value and it is preloaded intentionally.

常見原因:

  1. as 漏寫或寫錯:web.dev 說明缺漏或無效的 as 等同 XHR 請求,部分資源(如腳本)還可能被抓兩次。
  2. 資源在當前頁面沒有被用到:例如預載了首屏沒有文字使用的字型檔。
  3. 用 preload 預載下一頁資源:應改用 rel="prefetch"。

3. 疑問三:對響應式圖片使用 link preload(as="image")時,如何避免手機端下載桌機版大圖?

web.dev 說明可在 <link> 上使用 imagesrcset 與 imagesizes,瀏覽器會用與 srcset、sizes 相同的選圖邏輯發出預載請求;省略 href 可避免不支援 imagesrcset 的瀏覽器下載錯誤來源(但也得不到預載效益)。

若 <link> 寫死單一 href,而 <img> 使用 srcset,手機寬度(375 px)會同時請求 desktop-1600.webp 與 mobile-480.webp 兩張圖。改用範本二的 imagesrcset 與 imagesizes 並搭配相同的 srcset 後,375 px 只請求 hero-480.webp,1280 px 只請求 hero-1200.webp。

4. 疑問四:預載 ES Module 時,rel="modulepreload" 與 rel="preload" as="script" 有什麼差異?

  1. 處理深度:preload 只下載檔案並存入快取;modulepreload 還會解析、編譯模組,並把結果放進 module map,讓模組可立即執行。
  2. 請求模式:modulepreload 固定使用 cors 模式,as 預設為 script。

5. 疑問五:rel="preload" 與 rel="prefetch" 的用途有什麼不同?

  • rel="preload"(服務當前頁面):規範定義為預先抓取並快取當前導覽所需的資源。
  • rel="prefetch"(服務未來導覽):MDN 說明它用於同站導覽的資源,結果存在磁碟 HTTP 快取,瀏覽器通常給它比 preload 更低的優先權;no-cache、no-store 等 Cache-Control 標頭可能使它失效。文件本身的預先載入,MDN 建議在支援的瀏覽器改用 Speculation Rules API(<script type="speculationrules">),該 API 尚非 Baseline。

五、 總結

HTML Resource Hints 的核心原則,是依資源的使用時機與跨域屬性分級:藏在 CSS 內的關鍵字型與背景圖用 preload(字型必加 crossorigin)、ES 模組用 modulepreload、首屏一定會連的少數關鍵外部主機用 preconnect、次要外部網域用 dns-prefetch,下一頁才會用到的資源交給 prefetch。

下一個問題是 <head> 內 <meta charset>、viewport、<title>、preconnect、樣式表與同步腳本的排列順序,會如何影響預載掃描器與爬蟲解析,下一篇將逐一拆解。


參考資料與延伸閱讀

發佈留言