Skip to content

Speculation Rules 根據使用者意圖,在導覽前分流準備 document prefetch 與完整 prerender,並於 Navigation 時啟用下一頁

如何透過 Speculation Rules 預先準備下一個頁面? ​

前面介紹的 Resource Hints,可以提早解析 DNS、建立連線或取得 subresource。那如果現在想準備的不是一張圖片或一支 JavaScript,而是使用者接下來可能前往的完整頁面呢?

這時就可以使用 Speculation Rules API,把可能發生的下一次 Navigation 告訴瀏覽器。瀏覽器收到規則後,可以選擇先 prefetch document;如果下一頁的機率夠高,也能使用 prerender 提前完成更多工作,等使用者真的前往時再直接 activate。

聽起來很像按下連結後可以瞬間換頁,但先別急著把全站連結都加進去!Speculation Rules 用的是「預測」:猜對時可以縮短等待,猜錯時卻會白白消耗使用者和 Server 的資源。這篇就來看看它能準備到哪裡,以及要怎麼控制成本。

Prefetch 與 Prerender 差在哪? ​

Speculation Rules 目前最常使用的兩種動作是 prefetch 與 prerender:

  • prefetch:以未來 Navigation 的方式先取得 document,但不會先把頁面完整渲染出來。使用者真的前往時,瀏覽器可以重用準備好的 document,再繼續後面的解析與渲染工作。
  • prerender:除了取得 document,還會提前載入 subresource、執行 JavaScript 並準備頁面。實際 Navigation 發生時,瀏覽器再 activate 已經準備好的頁面。

所以 prefetch 的成本較低,但能省下的工作也比較少;prerender 更接近「把下一頁先在背景開好」,速度潛力更高,頻寬、CPU、記憶體與副作用也更多。

這兩種都只是提供候選規則。瀏覽器會依使用者設定、網路狀態、電量、記憶體與頁面是否符合條件,決定要不要真的執行;沒有辦法保證寫了規則就一定會 prefetch 或 prerender。

使用 URL List 指定頁面 ​

最直接的寫法,是在 <script type="speculationrules"> 裡列出 URL。假如使用者完成購物車後,很可能前往確認頁面,可以先從成本比較低的 prefetch 開始:

html
<script type="speculationrules">
  {
    "prefetch": [
      {
        "urls": ["/checkout/confirm"]
      }
    ]
  }
</script>

如果流程非常明確,而且頁面適合在背景完整執行,才進一步考慮 prerender:

html
<script type="speculationrules">
  {
    "prerender": [
      {
        "urls": ["/checkout/confirm"]
      }
    ]
  }
</script>

URL list 適合候選頁面很少,而且應用程式已經明確知道目標的情境。比方說多步驟表單的下一步、文章的下一頁,或使用者操作後幾乎一定會抵達的結果頁面。

規則中的 urls 可以使用相對或絕對 URL,但還是會受到同源、安全與瀏覽器實作限制。特別是 prerender,一般會先從 same-origin Navigation 開始使用;要處理 cross-origin 或 same-site cross-origin 頁面時,必須另外確認目標瀏覽器與 response opt-in 條件。

使用 Document Rules 篩選連結 ​

如果頁面上有很多連結,不想由程式逐一組出 URL,也可以使用 document rules。瀏覽器會查看目前文件中的 <a>,再依 where 條件選出候選連結。

以下範例會考慮站內連結,但排除登出頁面、購物車操作與加上 .no-prerender 的連結:

html
<script type="speculationrules">
  {
    "prerender": [
      {
        "where": {
          "and": [
            { "href_matches": "/*" },
            { "not": { "href_matches": "/logout" } },
            { "not": { "href_matches": "/cart/*" } },
            { "not": { "selector_matches": ".no-prerender" } }
          ]
        },
        "eagerness": "moderate"
      }
    ]
  }
</script>

href_matches 用 URL Pattern 篩選目標,selector_matches 則用 CSS selector 判斷 <a> 是否符合條件。and、or 與 not 可以組合多個規則,讓我們排除不應提前執行的操作。

早期範例可能還會看到 "source": "list" 或 "source": "document"。目前 Chromium 可以從 urls 或 where 推斷 rule source,因此已不再要求這個欄位。閱讀舊文章時看到 source 不用太意外,但新規則可以直接使用 urls 或 where。

Eagerness:要多早開始準備? ​

選出候選 URL 後,還要決定使用者意圖多明確時才開始準備。eagerness 可以設定以下四個值:

  • immediate:瀏覽器看到規則後就盡快開始,最積極,猜錯時也最容易浪費資源。
  • eager:比 immediate 保守,會等待較早期的使用者意圖或 viewport 線索。
  • moderate:等待更明確的互動訊號,例如桌面環境中使用者把 pointer 停在連結上一段時間。
  • conservative:等到 pointer 或 touch 已經按下等強烈訊號後才開始,浪費機率較低,但能提前完成的時間也最短。

Chromium 會持續調整不同裝置上的實際觸發 heuristic,所以不要把某個固定的毫秒數當成 API 保證。選擇時真正要考慮的是「準備時間」與「猜錯成本」的交換:越早開始,越有機會在 Navigation 前完成,也越可能準備到使用者根本不會看的頁面。

目前 URL list 沒有指定 eagerness 時,預設偏向 immediate;document rules 則偏向較保守的 conservative。即使如此,頁面只要有很多候選連結,仍然應該明確設定條件,不要把範圍交給預設值後就不管了。

實務上可以先從 prefetch 或較保守的 eagerness 開始,觀察命中率和資源消耗後,再決定是否提早到 eager、immediate,或升級成 prerender。

跟 Resource Hints 有什麼不同? ​

Resource Hints 與 Speculation Rules 都是在「提早做事」,所以很容易混在一起。可以先用準備目標來區分:

技術主要準備對象適合情境
dns-prefetch網域的 DNS 結果之後可能會連到的第三方 origin
preconnectDNS、TCP 與 TLS 等連線工作很快就會使用的 origin
preload當前頁面的關鍵 subresource確定會用到,但正常流程發現得太晚的資源
<link rel="prefetch">未來可能使用的 subresource下一步可能用到,而且不能影響當前頁面的資源
Speculation Rules prefetch未來可能前往的 document希望縮短下一次完整 Navigation 的文件取得時間
Speculation Rules prerender未來 document 與頁面執行狀態下一頁機率高,而且頁面適合在背景提前執行

也就是說,preload 解決的是當前頁面關鍵資源發現得太晚;Speculation Rules 解決的是下一次 document Navigation 還沒開始。雖然名稱裡都有 preload、prefetch 這種「先準備」的概念,但層級完全不同。

對 document Navigation 來說,現代 Chromium 建議使用 Speculation Rules;<link rel="prefetch"> 則保留給 subresource。完整的 Resource Hints 比較可以回頭看優化資源的載入時機。

Prerender 的副作用與限制 ​

prerender 會真的執行頁面,所以最需要注意的不是語法,而是頁面在「使用者還沒看到」時做了哪些事。

以下情境都要特別小心:

  • 頁面載入就送出 analytics page view,可能把最後沒有 activate 的 prerender 算成真實瀏覽。
  • JavaScript 載入後立刻修改 Server 狀態、扣庫存或送出表單,可能在使用者進入頁面前就產生副作用。
  • 廣告、即時連線、影片與第三方 script 可能提早消耗 CPU、流量或服務額度。
  • 頁面內容依登入狀態、購物車或即時資料產生,prerender 太早可能在 activate 時已經過期。
  • 大量候選頁面會增加使用者裝置的記憶體與頻寬壓力,也會讓 Server 收到最後沒有轉成真實瀏覽的 request。

頁面可以使用 document.prerendering 判斷目前是否正在 prerender,並把有副作用的工作延後到 activation:

javascript
if (document.prerendering) {
  document.addEventListener('prerenderingchange', () => {
    startAnalytics()
  }, { once: true })
} else {
  startAnalytics()
}

不過這不是把所有副作用包進判斷式就結束了。第三方 script、Server log、cache、認證資訊與資料新鮮度也要一起檢查,確保頁面真的適合 prerender。

怎麼確認規則有沒有生效? ​

首先可以檢查瀏覽器是否認得 speculationrules:

javascript
const supportsSpeculationRules =
  HTMLScriptElement.supports?.('speculationrules') ?? false

功能偵測只能說明瀏覽器支援語法,不代表每個候選頁面都會成功準備。Chrome DevTools 的 Application 面板提供 Speculative loads 檢視,可以查看 rule set、候選 URL、目前狀態與失敗原因。

驗證時不要只看「有沒有成功 prerender」,還要一起記錄:

  • 送出多少次 speculation,最後有多少次真的 activate。
  • Navigation 的等待時間是否有明顯下降。
  • 額外增加多少流量、Server request、CPU 與記憶體成本。
  • Analytics、廣告與狀態操作是否只在正確時間發生。

如果命中率很低,就應該縮小 URL 範圍、調整 eagerness,或從 prerender 降回 prefetch。效能變快固然很好,但不能把成本偷偷轉嫁給沒有前往下一頁的使用者!

總結 ​

Speculation Rules API 讓網站可以描述下一次可能發生的 document Navigation。prefetch 先準備 document,成本較低;prerender 連頁面解析、subresource 與 JavaScript 都會提前處理,activate 時能省下更多工作,但猜錯的代價也更高。

候選頁面可以透過 urls 明確列出,也能使用 document rules 的 where、href_matches 與 selector_matches 從頁面連結中篩選。接著再用 eagerness 控制要看到多強的使用者意圖才開始準備。

實務上最重要的不是選到最積極的規則,而是維持合理的成功命中率,並確認頁面在 prerender 階段不會產生錯誤副作用。下一個頁面還沒被使用就先準備,是 Speculation Rules;至於使用者按上一頁或下一頁時,瀏覽器如何直接恢復已經看過的頁面,則會在 BFCache 繼續處理。

參考資料 ​

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