Skip to content

preload、preconnect、prefetch:資源提示的正確用法

資源提示:preload、preconnect 與 prefetch

資源提示(Resource Hints)讓瀏覽器 提早開始處理 某個資源。它們是改善 LCP 的常用工具,但也是最容易用錯、反而拖慢載入的一組。

四種提示各解決什麼

提示解決什麼問題針對
preload資源 被發現得太晚當前頁面
preconnect跨網域的 連線交握太慢當前頁面
dns-prefetch只要省下 網域解析當前頁面
prefetch預測 下一個頁面 會用到下一個頁面

先問「問題是什麼」

不要因為看到別人用就照抄。每一種提示都對應一個具體的問題,沒有那個問題就不必用,用了只會浪費頻寬。

preload:資源被發現得太晚

瀏覽器要 讀到 資源的宣告才會開始下載。有些資源藏得很深:

藏在哪為什麼晚
CSS 的 background-image要等 CSS 下載並解析完
CSS 的 @font-face同上,還要算出有元素用到它
JavaScript 動態插入的資源要等程式碼執行
CSS 裡 @import 的檔案要等外層 CSS 解析完
html
<!-- 首屏的背景圖:提早開始下載 -->
<link rel="preload" as="image" href="/hero.webp" fetchpriority="high">

<!-- 首屏的字型 -->
<link rel="preload" as="font" type="font/woff2" href="/fonts/main.woff2" crossorigin>

as 是必填

as 告訴瀏覽器這是什麼資源,影響它的優先權與請求標頭:

用於
image圖片
font字型
styleCSS
scriptJavaScript
fetchfetch 取得的資料
videoaudio影音

沒寫 as 會下載兩次

瀏覽器無法判斷這是什麼資源時,會用不同的請求設定,結果是預載一次、實際使用時再下載一次,完全反效果

字型必須加 crossorigin

這是最常見的 preload 錯誤

html
<!-- 錯誤:會下載兩次 -->
<link rel="preload" as="font" href="/fonts/main.woff2">

<!-- 正確 -->
<link rel="preload" as="font" type="font/woff2" href="/fonts/main.woff2" crossorigin>

字型的請求一律以 匿名跨來源模式 發出(即使是同網域)。預載時不加 crossorigin,瀏覽器會認為那是兩個不同的請求。

只預載一到兩個

濫用 preload 會讓 LCP 變差

html
<!-- 反效果:十個資源互相搶頻寬 -->
<link rel="preload" as="font" href="/fonts/regular.woff2" crossorigin>
<link rel="preload" as="font" href="/fonts/bold.woff2" crossorigin>
<link rel="preload" as="font" href="/fonts/light.woff2" crossorigin>
<link rel="preload" as="image" href="/hero.webp">
<link rel="preload" as="image" href="/logo.svg">
<link rel="preload" as="image" href="/banner-1.webp">
<!-- ... -->

頻寬是固定的。 預載會提高優先權去搶頻寬,預載十個等於讓它們互相競爭,真正重要的那一個反而變慢。

原則:整站只預載首屏一到兩個關鍵資源,通常是 LCP 圖片與一個字型檔。

preconnect:提早建立連線

跨網域的資源要先做 DNS 查詢、TCP 連線、TLS 交握,三段加起來可能上百毫秒。preconnect 提早把這三段做完。

html
<!-- 字型服務:提早建立連線 -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>

字型檔的網域要加 crossorigin

fonts.googleapis.com 提供的是 CSS,fonts.gstatic.com 提供的是字型檔。後者要加 crossorigin,理由和 preload 相同。

適合不適合
首屏用到的第三方字型服務頁尾才載入的分析工具
首屏圖片的 CDN使用者可能不會觸發的第三方
關鍵的 API 網域自己的網域(已經連上了)

不要超過四個

每一個 preconnect 都會佔用資源並 保持連線一段時間(通常 10 秒),加太多會消耗頻寬與電力,行動裝置尤其明顯。

dns-prefetch:只省網域解析

preconnect 輕量,只做 DNS 查詢,不建立連線。

html
<link rel="dns-prefetch" href="https://analytics.example.com">
preconnectdns-prefetch
做什麼DNS + TCP + TLS只有 DNS
成本較高很低
效益較大較小
適合首屏關鍵的少數網域稍後才會用到的較多網域

兩者可以並用當退路

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

不支援 preconnect 的環境會落到 dns-prefetch。現在支援度已經很好,這個寫法的必要性降低了,但寫了也沒壞處。

prefetch:為下一個頁面準備

它針對的是 下一個頁面,優先權很低,只在瀏覽器閒置時下載。

html
<!-- 使用者很可能接著看的頁面 -->
<link rel="prefetch" href="/seo/core-web-vitals-lcp">

預測錯就是純浪費

使用者沒去那一頁的話,下載的流量完全白費,行動網路上這是實際的成本。

只在 有明確的下一步 時使用:結帳流程的下一步、多步驟表單、閱讀順序明確的教學系列。

多數框架的路由套件已經內建這個行為(例如:滑鼠移到連結上時才預先擷取),不必自己手寫。

modulepreload

ES 模組專用的預載,會連同它的依賴一起處理:

html
<link rel="modulepreload" href="/app.js">
preload + as="script"modulepreload
下載
解析
處理依賴是(遞迴)

建置工具通常自動處理

Vite 這類工具在建置時會自動插入 modulepreload,並為不支援的瀏覽器加上 polyfill。手動加的必要性很低。

fetchpriority:相對的優先權

它不是提示,而是 調整既有請求的優先權

html
<!-- 首屏主視覺:提高 -->
<img src="/hero.webp" alt="主視覺" fetchpriority="high" width="1200" height="400">

<!-- 首屏之外的裝飾圖:降低 -->
<img src="/decoration.webp" alt="" fetchpriority="low" loading="lazy">
意義
high比同類資源優先
low比同類資源延後
auto交給瀏覽器決定(預設)

它是相對的,不是絕對的

全部資源都設成 high 等於沒設,互相比較的結果沒有變。

實務上 整頁只該有一到兩個資源設為 high,其餘維持 auto 或降為 low

fetchpriority 也可以用在 preloadfetch() 上:

html
<link rel="preload" as="image" href="/hero.webp" fetchpriority="high">
js
// 不重要的請求降低優先權,不要跟首屏資源搶
fetch('/api/analytics', { priority: 'low' });

一份合理的組合

大多數網站需要的就這幾行:

html
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">

  <!-- 首屏用到的第三方字型服務(若有) -->
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>

  <!-- 首屏的關鍵字型(只一個) -->
  <link rel="preload" as="font" type="font/woff2" href="/fonts/main.woff2" crossorigin>

  <title>頁面標題|網站名稱</title>
</head>
<body>
  <!-- LCP 圖片直接寫在 HTML 裡,不需要 preload -->
  <img src="/hero.webp" alt="主視覺" width="1200" height="400" fetchpriority="high">
</body>

HTML 裡的 img 通常不需要 preload

瀏覽器的預載掃描器會提早發現 HTML 裡的 <img>需要 preload 的是藏在 CSS 或 JavaScript 裡的資源。

直接寫在 HTML 的圖片只要加 fetchpriority="high" 就夠了。

這個站的做法

這個站 一個資源提示都沒有手寫,因為沒有需要解決的問題:

為什麼不需要說明
不用網頁字型用系統字型堆疊,沒有字型要預載
橫幅是 HTML 裡的 <img>預載掃描器自己會發現
橫幅是 SVG,只有幾 KB下載時間本來就很短
沒有第三方字型或圖片服務沒有跨網域連線要提早建立

建置工具還是會自己加

檢視原始碼會看到 modulepreload 與樣式的 preload,那是 Vite 依實際的模組相依關係自動產生的,不是手寫的。

這正是本篇的原則:該由工具判斷的交給工具,人只在有具體問題時才手動加提示。

只有橫幅圖用了 fetchpriority="high"

markdown
![說明文字](/images/seo/core-web-vitals-resource-hints-banner.svg){ width=1200 height=400 fetchpriority="high" }

沒有問題就不要加提示

這是本篇最重要的一句。資源提示不是「加了就會更快」的東西,它是針對特定問題的工具,濫用會反過來拖慢。

檢查清單

項目標準
每個提示都對應一個具體問題必備
preload 不超過兩個必備
preload 都有寫 as必備
字型的 preloadcrossorigin必備
preconnect 不超過四個建議
fetchpriority="high" 不超過兩個必備
HTML 裡的 <img> 沒有多餘的 preload建議
prefetch 只用在有明確下一步時建議
加了提示後有量測是否真的改善建議

常見問題

四種提示怎麼選?

看您要解決哪個問題。資源被發現得太晚用 preload;跨網域連線的交握太慢用 preconnect;只要省下網域解析用 dns-prefetch;預測使用者下一步會去哪用 prefetch。前三者針對當前頁面,最後一個針對下一個頁面。

為什麼濫用 preload 會拖慢載入?

因為頻寬是固定的。預載的資源會提高優先權去搶頻寬,預載了十個資源等於讓它們互相競爭,真正重要的那一個反而變慢。原則是整站只預載一到兩個首屏關鍵資源。

fetchpriority 設成 high 就一定會先載嗎?

它是相對的優先權,不是絕對的。全部資源都設成高等於沒設,因為互相比較的結果沒有變。實務上整頁只該有一到兩個資源設為高,其餘維持預設或降為低。

preconnect 可以加幾個?

建議不超過四個。每一個預先連線都會佔用資源並保持連線一段時間,加太多反而消耗頻寬與電力。只對首屏真正需要的關鍵第三方網域使用。

字型預載需要 crossorigin 嗎?

需要,而且是必填。字型的請求一律以匿名跨來源模式發出,預載時若不加這個屬性,瀏覽器會認為是兩個不同的請求而下載兩次,完全失去預載的意義。

延伸閱讀

參考資料:web.dev:Optimize resource loading