ServiceWorkerの課題を解決する!? Static Routing APIを使ってみた

はじめに

こんにちは、Repro Booster という製品の開発責任者/プロダクトマネジメントを担当しているEdward Fox(@edwardkenfox)です。

今回は、ServiceWorkerに組み込まれた新機能「Static Routing API」を実際に試してみた件について説明します。Repro Boosterでの応用を通じて得た知見を共有できればと思います。

Repro Boosterとは

Repro Boosterは、私たちが提供しているパフォーマンス最適化ソリューションです。サイトにタグを設置するだけで、読み込み速度が向上するというシンプルなコンセプトで設計されています。

その鍵となる技術の一つがServiceWorkerの活用です。ServiceWorkerはブラウザのバックグラウンドで動作し、リソースキャッシュやネットワークの中継など、従来のWebサイトでは実現できなかった機能が実装できる強力なコンポーネントです。しかし、この便利な仕組みにもいくつかの課題があります。

課題

ServiceWorkerは非常に強力ですが、それまで存在しなかったネットワークレイヤーが加わるため、すべてのリクエストを処理することで余分なオーバーヘッドを発生させる可能性があります。この点については、以前の記事(「ServiceWorkerの落とし穴8選」)でも触れました。

またこれ以外にも、次のような潜在的な課題が挙げられます。

  • GET以外のリクエストの扱い:データ送信を伴うPOSTリクエストなどへの対応が複雑で、ServiceWorkerの導入によりサイトの挙動やサーバーとの通信が改変されてしまうリスクがあります。
  • 不要なリソースの処理の扱い:特定の種類のリソースだけを扱いたい場合でも、すべてのリクエストがServiceWorkerのfetchハンドラを経由してしまいます。

こうした課題に対処するために考案されたのが Static Routing API です。

Static Routing APIとは

Static Routing API は、ServiceWorkerのオーバーヘッドを削減し、安全性を向上させるための新しい仕組みです。この機能を使うことで、特定の条件に合致するリクエストだけをServiceWorkerで処理できるようになります。

例えば、以下のような効果が期待できます:

  • オーバーヘッドの削減:ServiceWorkerを介さないリクエストが増えることで、FCP(First Contentful Paint)やLCP(Largest Contentful Paint)などのパフォーマンス指標が改善する可能性がある。
  • 安全性の向上:GET以外のリクエストや、不要なリソースへの対応を自動的に排除することで、意図しない挙動を防止する。

今回は、このStatic Routing APIを実際にRepro Boosterに組み込んで試してみた結果をお伝えします。

実装

以下は、Static Routing APIを利用した実装例です。

self.addEventListener('install', (event: ExtendableEvent) => {
  if ('addRoutes' in event) {
    event.addRoutes({
      condition: {
        not: {
          requestMethod: 'get',
        },
      },
      source: 'network',
    });

    event.addRoutes({
      condition: {
        or: [
          { requestDestination: 'audio' },
          { requestDestination: 'audioworklet' },
          { requestDestination: 'embed' },
          { requestDestination: 'fencedframe' },
          { requestDestination: 'font' },
          { requestDestination: 'json' },
          { requestDestination: 'manifest' },
          { requestDestination: 'object' },
          { requestDestination: 'paintworklet' },
          { requestDestination: 'report' },
          { requestDestination: 'sharedworker' },
          { requestDestination: 'style' },
          { requestDestination: 'track' },
          { requestDestination: 'video' },
          { requestDestination: 'worker' },
          { requestDestination: 'xslt' },
        ],
      },
      source: 'network',
    });
  }
});

上記の実装は次のような挙動を実現します。

  1. GET以外のリクエストについてはServiceWorkerを経由せず(fetchハンドラが起動せず)ネットワークに直接出ていく
  2. audioやvideoなどのリソースについても同様に、ServiceWorkerを経由せずネットワークに直接出ていく
    • document や image など、Repro Boosterが扱いたいリソースについては condition に記述しておらず、狙い通りServiceWorkerを通ることになります

ChromeのDevtoosでは、ServiceWorkerが登録された際のルーティングを確認できるUIも用意されています。便利ですね。

結果

以下の点については、想定通りのポジティブな結果として挙げられます。

  • 安全性の向上:POSTリクエストや不要なリソースに対して、ServiceWorkerが関与しなくなったこと。
  • ネットワークログの改善:Networkタブで "ServiceWorker経由" と表示されるリクエストが減少しました。

一方で、パフォーマンスへの影響は想定していたような効果がハッキリと見えたわけではなく、傾向の変化や好影響は特になかった、と現時点では結論付けています。また、次の点はまだ課題として残ります。

  • Safariの非対応:このAPIはまだSafariなどChrome以外のブラウザではサポートされておらず、非対応のブラウザでは単に無視されてしまいます。非対応ブラウザの、引き続きコード上でも Static Routing API と同じルールで NOOP になる分岐を入れる必要が残ります。
  • 型定義の欠如@types/serviceworkerというパッケージはあるものの、Chrome以外のブラウザでもStatic Routing APIが実装されないと型定義が生成されないようです。
  • 変化の観測の難しさ: PerformanceResourceTiming API など利用してServiceWorkerのオーバーヘッドのみを計測することができず、微細な変化の計測に課題が残ります。

ChromeにおいてはStatic Routing APIが効くことで関心のないリクエストを触ることがなくなったため、その点では安全性は一定向上したと言えるでしょう。しかしながらSafariでは非対応であり、fetchハンドラを書く際にはGETや関心のあるリソース以外のリクエストについても考慮したコードを書く必要が残るため、心理的な変化や認知負荷を低下させる効果は期待したほど(現時点ではまだ)なかった、といった印象です。

まとめ

Static Routing APIは、ServiceWorkerの課題を解決するための有望な技術です。速度改善についてはまだ結論が出せませんが、安全性の向上や余計なリソース処理の排除といった点で多少の効果はあったと言えるでしょう。また、ServiceWorker周辺では "race-network-and-cache" など新しい提案も進行中です(参考:GitHub Pull Request)。これからの進化に期待しつつ、今後も追って検証を続けていきたいと思います。

最後に、Repro Boosterでは開発者を採用しています。「タグを入れる」だけでサイトを速くするRepro Boosterの技術に興味を持っていただけた方は、ぜひ気軽にご連絡ください。