🥒

Cloudflare で投機ルール API をアドオンし、先回りレンダリング

2024/08/16に公開

はじめに

以前の記事で Cloudflare Dev docs が投機ルール API のプリフェッチを利用していた件に触れました。

Chrome for Developers Blog の Speculation Rules API を改善 ページをみると、Akamai CDN で自分のサイトにこの API を有効にするがありましたので、Cloudflare 版を試します。

方針

プロキシー側で実装する場合、元の HTML ドキュメントに投機ルールを挿入する方式より、HTTP レスポンスヘッダーを追加し、投機ルールの配信先に誘導する方が素直だと思うので、そちらで試します。

具体的には Cloudflare で

・レスポンスヘッダー Speculation-Rules を追加
・上記ヘッダーに設定した URL から投機ルールの定義ファイルを配信

することで、プリフェッチ事前レンダリングを実現します。

Codelab に投機ルール API のチュートリアルがあります(オリジン側での事前レンダリング実装)。
その環境を一部利用し、プロキシー側での実装に切り替えても同様の効果が出るかを確認します。

準備

オリジン

オリジンは Codelab に記載してある Glitch の内容をそのまま利用させてもらいます。
ただ、③デモサイトを作成する までにします。
デモサイトには効果測定のための人為的な遅延が仕込まれています。

これで、デモサイトの設定は完了です。クリックして動作を確認してください。ページ 2 と 3 は、メインスレッドの人為的なブロックにより、読み込みに時間がかかっているはずです。
:
ブロック中というデバッグ メッセージと、このページの LCP の表示に 3,658 ミリ秒かかったことも確認できます。これは、ダミーのブロック関数によりメインスレッドがブロックされていることによるものです。

実装

ヘッダー挿入とファイル配信なのでいくつか方法が考えられますが、今回は下記 2 つの方式を試します。

  1. Workers
  2. Rules と R2

1. Workers

Workers だけで済ます例です。

wrangler.toml
routes = [
  { pattern = "fast.oymk.work", custom_domain = true }
]
index.ts
  • 投機ルール定義ファイルの MIME タイプは application/speculationrules+json指定
  • Glitch オリジンは user-agent がないと動かなかったのでリクエストヘッダーに追加
import { Hono } from 'hono'

export interface Env {}

const app = new Hono<{ Bindings: Env }>()

app.get('/speculationrules.json', (c) => {
 return c.json({
  prerender: [{
   where: {
    and: [{
     href_matches: "/*",
     relative_to: "document"
    }]
   },
   eagerness: "moderate"
  }]
 }, 200, {
 'content-type': 'application/speculationrules+json',
 })
})

app.get('/*', async (c) => {
 const url = new URL(c.req.url)
 const res = await fetch("https://apple-sixth-seaplane.glitch.me" + url.pathname, {
  headers: {
   'user-agent': 'cf-worker',
  }
 })
 const newRes = new Response(res.body, res)
 newRes.headers.set('Speculation-Rules', '"/speculationrules.json"')
 return newRes
})

export default app

wrangler tail でリクエストの着信を見ます。
投機ルール定義ファイルの eagernessmoderate だと、Page 2 にカーソルを合わせた時点で、リンク先リソースやサブリソースにアクセスが発生してます。また、prerender 指定で事前レンダリングが行われるので、(仕込まれたブロック関数の時間が経過した)3 秒より後でクリックするとすぐ描画されます。

eagernessimmediate に変えると、事前レンダリングのタイミングが変わり、index.html ロードの後に即発動されます。

2. Rules と R2

RulesR2 で実装する例です。

投機ルール定義ファイルの配信

R2(Public bucket)から配信します。

  • Bucket を作成し、R2.dev での公開を許可
    カスタムドメインが望ましいですが、テストなのでサクッと R2.dev を使います。

  • CORS 指定
    クロスオリジンのケースになるので CORS ポリシーを指定します。

  • Bucket に Object (投機ルール定義ファイル)を配置

ルール定義ファイル
speculation.json
{
  "prerender": [{
   "where": {
    "and": [{
     "href_matches": "/*",
     "relative_to": "document"
    }]
   },
   "eagerness": "moderate"
  }]
 }
R2 へアップロード

MIME タイプを指定しています。

❯ npx wrangler r2 object put 0-pub/speculationrules.json --file=speculation.json --content-type=application/speculationrules+json --cache-control=max-age=60

👉 wrangler commands

  • Object に付与された R2.dev URL を確認
    R2 > 対象 Bucket > 対象 Object
    この URL が Speculation-Rules ヘッダーの値となるので、メモしておきます。
Cloudflare プロキシー設定

Glitch オリジンで動作させるための設定です。
他のオリジンの場合はそれぞれ合わせた調整になります。

Value は 投機ルール定義ファイルの R2 Object URL("..." で囲うこと)

その他

Page Shiled で CSP の調整など、セキュリティ機能の利用状況に応じてつじつまを合わせる。

LCP time 結果

下記の通り、オリジン側で投機ルール API を実装した Codelab のデモと同じ結果を Cloudflare プロキシーでの実装でも得られることを確認しました。

事前レンダリングによって LCP time が短縮される理由は下記のとおりです。

この例ではまた、有効化の時間が 4,298 ミリ秒である一方、LCP の時間はそれよりはるかに短い 128 ミリ秒であることもわかります。パフォーマンス指標の時間測定はすべて、ページが読み込みを開始した時点を基準として行われます。ただし、LCP の記録に使われるウェブに関する指標のライブラリは、ユーザーの視点で LCP を測定するため、activationStart の時間を差し引く特殊なロジックとなっています。Chrome ユーザー エクスペリエンス レポート(CrUX)でもこの方法で LCP を算出しており、Google Search Console の PageSpeed Insights や Core Web Vitals レポートなどのツールもこれに準拠しています。
Chrome for Developers

投機ルール API なし

Page 2: 3836ms
Page 3: 4545ms

シーケンス(簡略化)

投機ルール API あり

Page 2: 78ms
Page 3: 67ms

シーケンス(簡略化)
  • 事前レンダリング moderate
  • 事前レンダリング immediate
事前レンダリングの途中でクリックが発生したら?

その分だけ表示までの時間が短縮されます。

リンクをクリックすると、事前レンダリングされたページが読み込まれます。リンクをクリックするまでに、ページを読み込む十分な時間があった場合は一瞬で表示されます。ページの読み込みが完了していなかった場合でも、ある程度速く表示されます。いずれにしても、投機ルールを使用しない場合と比べて LCP の時間は短くなります。
Chrome for Developers

以上、Cloudflare で投機ルール API を追加するサンプルでした。

参考ページ

https://codelabs.developers.google.com/speculation-rules?hl=ja#0
https://developer.chrome.com/docs/web-platform/prerender-pages?hl=ja#eagerness
https://developer.mozilla.org/ja/docs/Web/API/Speculation_Rules_API
https://developer.mozilla.org/ja/docs/Web/Performance/Speculative_loading

Discussion