Gestión de presupuesto de prefetch de imágenes en Service Worker 2025 — Reglas de prioridad e INP saludable

articles.published: 29 sept 2025 · articles.readingTime: 5 articles.minutes · articles.byEditorial

Muchos equipos añaden prefetch de imágenes para mejorar LCP y terminan con un Service Worker que acapara ancho de banda y empeora INP. En 2025 contamos por fin con Priority Hints estables y señales más fiables del Network Information API, lo que nos permite modular el prefetch de forma dinámica. Este artículo trata el conjunto de assets y el momento de precarga como un “presupuesto” y explica cómo precargar héroes o galerías sin degradar la experiencia.

TL;DR

  • Cuantifica el presupuesto: calcula budget = (downlink × 0.25) - recursos LCP en curso y pausa el prefetch cuando el resultado sea negativo.
  • Reordena dentro del Service Worker: registra telemetría de Navigation Timing + INP y ajusta el ranking para la siguiente visita de manera automática.
  • Conecta Priority Hints con fetchpriority: sobrescribe el valor HTML (low) desde el Service Worker y cambia a auto/high cuando las condiciones lo permitan.
  • Reintenta con Background Sync: cancela en sesiones offline o limitadas y reprográmalo vía periodicSync durante la noche.
  • Observabilidad: registra el éxito del prefetch y la ΔLCP en performance-guardian para vigilar si el presupuesto está bien calibrado.

Diseñar el modelo de presupuesto

MétricaCómo obtenerlaCadencia recomendadaObjetivo
downlinknavigator.connection.downlinkInicio de sesión y cambios de redEstimación de ancho de banda
effectiveTypeNetwork Information APICada ejecuciónClasificar 3G/4G/5G
inpP75PerformanceObserver + RUMCada ejecuciónDisparar alertas al degradarse INP
lcpCandidateSizeperformance.getEntriesByType('largest-contentful-paint')Cuando se fija el LCPConocer el tamaño del asset LCP
prefetchSuccessRateLogs del Service WorkerA diarioEvaluar el impacto del prefetch

El prefetch no siempre es la respuesta; usa estas señales para decidir si existe presupuesto en ese momento.

// 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)
}

Construir la cola de prefetch

Gestiona los candidatos en 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
  }
]

El Service Worker lee el manifiesto y solo encola lo que cabe dentro del presupuesto disponible.

// 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 sigue siendo experimental pero funciona en Chrome y Safari. Para los navegadores que no soportan la opción priority, implementa un fallback que reescriba el atributo <link fetchpriority>.

Coordinar Priority Hints con 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
            }
          });`
        }}
      />
    </>
  )
}

Estrategia de cancelación para proteger INP

Si INP empeora, detén el prefetch de inmediato y reduce la prioridad en futuras sesiones.

// 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 congela la cola actual; reducir priority en 0.1 hace que las páginas con interacciones pesadas limiten el prefetch por sí mismas.

Background Sync y prefetch nocturno

Forzar el prefetch en conexiones pobres bloquea la interfaz. Usa periodicSync para reintentar cuando haya Wi-Fi o en horas valle.

// 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
}

Monitorización y analítica

Envía eventos RUM a performance-guardian para comprobar si el prefetch aporta valor.

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

Tablas clave del dashboard:

KPIReferenciaCondición de alerta
ΔLCP (prefetch vs sin prefetch)≈ -180 msValores positivos durante 3 días seguidos
INP p75< 180 ms> 200 ms → detener de inmediato
Prefetch Success Rate> 85%< 70% → reajustar manifiesto
Consumo de ancho de banda< 30%> 50% → pausar experimento

Checklist

  • [ ] prefetch-manifest.json pasa por revisión de código.
  • [ ] Los parámetros de calculateBudget se validan con tests A/B.
  • [ ] El prefetch se detiene al instante si INP empeora.
  • [ ] Background Sync reintenta solo en Wi-Fi.
  • [ ] performance-guardian grafica ΔLCP y ΔINP.
  • [ ] Se compara la tasa de aciertos de caché CDN antes y después del despliegue.

Resumen

El prefetch gestiona mal puede consumir ancho de banda y dañar INP. Con un modelo de presupuesto, priorización dinámica y Background Sync, puedes condicionar el prefetch para que LCP mejore sin arruinar la experiencia. Combina APIs del navegador con automatización en CI para construir bucles de control y evoluciona la estrategia de prefetch según el sitio.

articles.related

Compresión

Control de streaming consciente de la pérdida 2025 — Gobernar el ancho de banda AVIF/HEIC con SLO de calidad

Guía práctica para equilibrar la limitación de ancho de banda y los SLO de calidad al entregar formatos de alta compresión como AVIF/HEIC. Incluye patrones de control de streaming, monitoreo y estrategias de rollback.

Flujo de trabajo

Automatización de la optimización de imágenes con un pipeline WASM 2025 — Recetas con esbuild y Lightning CSS

Patrones para automatizar la generación, validación y firma de derivados de imagen con una cadena de compilación habilitada para WASM. Explica cómo integrar esbuild, Lightning CSS y Squoosh CLI para obtener CI/CD reproducible.

Web

Optimización de Entrega de Imágenes 2025 — Guía de Priority Hints / Preload / HTTP/2

Mejores prácticas para la entrega de imágenes que no sacrifican LCP y CLS. Combina Priority Hints, Preload, HTTP/2 y estrategias de formato apropiadas para equilibrar el tráfico de búsqueda y la experiencia del usuario.

Compresión

Estrategia Definitiva de Compresión de Imágenes 2025 — Guía Práctica para Optimizar Rendimiento Preservando Calidad

Desglose integral de las últimas estrategias de compresión de imágenes para Core Web Vitals y operaciones del mundo real, con presets específicos, código y flujos de trabajo por caso de uso. Cubre selección JPEG/PNG/WebP/AVIF, optimización build/entrega y resolución de problemas.

Web

Auditor de niveles de servicio CDN 2025 — Monitoreo SLA con evidencia real para delivery de imágenes

Arquitectura de auditoría para demostrar cumplimiento SLA en despliegues multi-CDN. Cubre estrategia de medición, recolección de evidencias y reportes listos para negociación.

Web

Monitoreo práctico de Core Web Vitals 2025 — Checklist SRE para proyectos enterprise

Playbook orientado a SRE que ayuda a los equipos de producción web enterprise a operacionalizar Core Web Vitals, cubriendo diseño de SLO, recolección de datos y respuesta a incidentes de extremo a extremo.