Service Worker画像プリフェッチ予算管理 2025 — Priority RulesとINP健全化の実践

公開: 2025年9月29日 · 読了目安: 6 · 著者: Unified Image Tools 編集部

LCP 改善のために画像プリフェッチを導入したものの、Service Worker が帯域を使いすぎて INP が悪化する、という報告が増えています。2025 年は Priority Hints の安定実装と、Network Information API の指標精度向上により、プリフェッチを動的に制御する土壌が整いました。本稿では、プリフェッチ対象とタイミングを「予算」として管理し、ユーザー体験を損なわずにヒーロー画像やギャラリーを先読みする方法を、コーダー視点でまとめます。

TL;DR

  • プリフェッチ予算を数値化: budget = (下り帯域 × 0.25) - 同時ロード中のLCP資産 として算出し、マイナスならプリフェッチを中断。
  • Service Workerで優先度を再計算: Navigation Timing・INP テレメトリを収集し、次回訪問時にプリフェッチ対象の rank を更新。
  • Priority Hintsとfetchpriorityの連携: HTML に仕込んだ fetchpriority="low" を Service Worker で上書きし、状況に応じて auto / high に切り替え。
  • Background Syncで再試行: オフライン・低帯域時はキャンセルし、periodicSync で夜間に再プリフェッチ。
  • Observability: performance-guardian のエッジログにプリフェッチ成功率と ΔLCP を記録し、予算が適切か継続監視。

予算モデルの設計

指標取得方法推奨頻度目的
downlinknavigator.connection.downlinkセッション開始、ネットワーク変化時帯域推定
effectiveTypeNetwork Information API毎回3G/4G/5G 判定
inpP75PerformanceObserver + RUM毎回INP 劣化防止の警告トリガ
lcpCandidateSizeperformance.getEntriesByType('largest-contentful-paint')最初のLCP確定時LCP 資産のサイズ把握
prefetchSuccessRateService Worker ログ日次プリフェッチ効果の評価

プリフェッチは常に正解ではないため、上記の指標を基に「今は予算があるか」を判断します。

// sw/budget.ts
export function calculateBudget({ downlink, lcpSize, concurrentLoads }: {
  downlink: number
  lcpSize: number
  concurrentLoads: number
}) {
  const capacity = downlink * 125000 // Mbps -> bytes/s
  const reserved = lcpSize + concurrentLoads * 150000
  return Math.max(0, capacity * 0.25 - reserved)
}

プリフェッチキューの構築

プリフェッチ候補は prefetch-manifest.json で管理します。

[
  {
    "id": "hero-day2",
    "url": "/images/event/day2@2x.avif",
    "priority": 0.9,
    "type": "image",
    "expectedSize": 320000
  },
  {
    "id": "gallery-mini",
    "url": "/images/gallery/thumbs.webp",
    "priority": 0.4,
    "type": "image",
    "expectedSize": 90000
  }
]

Service Worker ではこのマニフェストを読み込み、予算内に収まるものだけをキューに追加します。

// sw/prefetch.ts
import { calculateBudget } from './budget'
import manifest from '../prefetch-manifest.json'

self.addEventListener('message', event => {
  if (event.data?.type !== 'INIT_PREFETCH') return
  const state = event.data.state
  const budget = calculateBudget({
    downlink: state.downlink,
    lcpSize: state.lcpSize,
    concurrentLoads: state.concurrentLoads
  })
  const queue = manifest
    .filter(item => item.expectedSize <= budget)
    .sort((a, b) => b.priority - a.priority)
  prefetchQueue(queue)
})

async function prefetchQueue(queue) {
  for (const entry of queue) {
    const controller = new AbortController()
    const timeout = setTimeout(() => controller.abort(), 4000)
    try {
      await fetch(entry.url, {
        priority: entry.priority > 0.7 ? 'high' : 'low',
        signal: controller.signal
      })
      await caches.open('prefetch-v1').then(cache => cache.add(entry.url))
      logPrefetch(entry.id, true)
    } catch (error) {
      logPrefetch(entry.id, false, error)
    } finally {
      clearTimeout(timeout)
    }
  }
}

fetchpriority はまだ実験的ですが、Chrome/Safari で使用可能です。priority オプションが未対応のブラウザでは fetchpriority 属性を書き換える fallback を実装します。

Priority HintsとHTMLの連携

// app/layout.tsx
export function PrefetchHints() {
  return (
    <>
      <link
        rel="preload"
        as="image"
        href="/images/event/day2@2x.avif"
        fetchPriority="low"
      />
      <script
        dangerouslySetInnerHTML={{
          __html: `navigator.serviceWorker?.controller?.postMessage({
            type: 'INIT_PREFETCH',
            state: {
              downlink: navigator.connection?.downlink || 1.5,
              lcpSize: window.__LCP_SIZE__ || 200000,
              concurrentLoads: window.__IN_FLIGHT__ || 0
            }
          });`
        }}
      />
    </>
  )
}

INPを守るためのキャンセル戦略

INP が悪化した場合、即座にプリフェッチを中断し、次回以降の優先度を下げます。

// sw/inp-monitor.ts
const INP_THRESHOLD = 200

new PerformanceObserver(list => {
  for (const entry of list.getEntries()) {
    if (entry.duration > INP_THRESHOLD) {
      self.registration.active?.postMessage({ type: 'CANCEL_PREFETCH' })
      updatePriority(entry.eventType)
    }
  }
}).observe({ type: 'event', buffered: true })

CANCEL_PREFETCH でキューを停止し、priority を 0.1 ずつ減少させるアルゴリズムを採用すると、重いユーザー アクションが発生したページでは自動的にプリフェッチが抑制されます。

Background Sync と夜間プリフェッチ

低帯域やオフライン時にプリフェッチを強行すると、ページ操作が詰まります。periodicSync を使って夜間や Wi-Fi 接続時に再実行します。

// sw/background-sync.ts
self.addEventListener('sync', event => {
  if (event.tag !== 'prefetch-sync') return
  event.waitUntil(prefetchQueue(manifest))
})

async function scheduleSync() {
  const registration = await self.registration.periodicSync?.register('prefetch-sync', {
    minInterval: 6 * 60 * 60 * 1000
  })
  return registration
}

モニタリングと分析

performance-guardian のカスタムイベントを使ってプリフェッチ効果を RUM に送信します。

sendToAnalytics('prefetch', {
  budget,
  downlink,
  prefetchSuccessRate,
  deltaLCP,
  deltaINP
})

ダッシュボードでは以下の KPI をトラッキングします。

KPI目安アラート条件
ΔLCP (プリフェッチ有無)-180ms 程度正の値が 3 日継続
INP p75< 180ms200ms 超で即停止
Prefetch Success Rate> 85%70% 未満でマニフェスト再調整
帯域占有率< 30%50% 超で AB テスト停止

チェックリスト

  • [ ] prefetch-manifest.json が PR レビュー対象になっている
  • [ ] calculateBudget のパラメータが A/B テストで検証済み
  • [ ] INP 悪化時にプリフェッチが即停止する
  • [ ] Background Sync で Wi-Fi 時のみ再プリフェッチする
  • [ ] [performance-guardian](/ja/tools/performance-guardian) で ΔLCP, ΔINP がダッシュボード化されている
  • [ ] CDN のキャッシュヒット率がプリフェッチ導入前後で比較されている

まとめ

Service Worker による画像プリフェッチは、無計画に導入すると帯域と INP を圧迫します。予算モデル、優先度の動的再計算、Background Sync の活用によってプリフェッチを状況依存にし、ユーザー体験を守りながら LCP を改善しましょう。Web コーダーは、ブラウザ API と CI 実装を組み合わせて監視・制御ループを自動化し、サイトごとの最適なプリフェッチ戦略を育てていく必要があります。

関連記事

圧縮

損失管理型ストリーミングスロットリング 2025 — AVIF/HEIC帯域制御と品質SLO

AVIF/HEICなどの高圧縮フォーマットを配信する際に、帯域スロットリングと品質SLOを両立させるためのストリーミング制御と監視手法を整理。

ワークフロー

WASMビルドパイプラインで画像最適化を自動化 2025 — esbuildとLightning CSSの統合レシピ

WASM対応ビルドチェーンで画像の派生生成・検証・署名を自動化する実装パターン。esbuild、Lightning CSS、Squoosh CLI を統合し、CI/CDで耐障害性を確保する方法を整理。

圧縮

画像圧縮 完全戦略 2025 ─ 画質を守りつつ体感速度を最適化する実践ガイド

Core Web Vitals と実運用に効く最新の画像圧縮戦略を、用途別の具体的プリセット・コード・ワークフローで徹底解説。JPEG/PNG/WebP/AVIF の使い分け、ビルド/配信最適化、トラブル診断まで網羅。

Web

CDNサービスレベル監査 2025 — 画像配信SLAを可視化する監査基盤

マルチCDN環境で画像SLAを証明するための監査アーキテクチャ。計測指標、証跡収集、ベンダー交渉に使えるレポーティング手法を紹介。

Web

Core Web Vitals 実践モニタリング 2025 — エンタープライズ案件のSREチェックリスト

エンタープライズ規模のWeb制作チームがCore Web Vitalsを継続監視するためのSRE運用テンプレート。SLO設計、メトリクス収集、インシデント対応まで包括的に解説します。

圧縮

Edge画像配信オブザーバビリティ 2025 — Web制作会社のSLO設計と運用手順

Edge CDNとブラウザでの画像配信品質を観測するためのSLO設計、計測ダッシュボード、アラート運用をWeb制作会社向けに詳解。Next.jsとGraphQLを使った実装例付き。