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 aauto
/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étrica | Cómo obtenerla | Cadencia recomendada | Objetivo |
---|---|---|---|
downlink | navigator.connection.downlink | Inicio de sesión y cambios de red | Estimación de ancho de banda |
effectiveType | Network Information API | Cada ejecución | Clasificar 3G/4G/5G |
inpP75 | PerformanceObserver + RUM | Cada ejecución | Disparar alertas al degradarse INP |
lcpCandidateSize | performance.getEntriesByType('largest-contentful-paint') | Cuando se fija el LCP | Conocer el tamaño del asset LCP |
prefetchSuccessRate | Logs del Service Worker | A diario | Evaluar 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:
KPI | Referencia | Condición de alerta |
---|---|---|
ΔLCP (prefetch vs sin prefetch) | ≈ -180 ms | Valores 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.
Herramientas relacionadas
Guardián del rendimiento
Modela presupuestos de latencia, rastrea incumplimientos de SLO y exporta evidencias para revisiones de incidentes.
Comparador
Comparación antes/después intuitiva.
Presupuestos de calidad de imagen y puertas CI
Define presupuestos de ΔE2000/SSIM/LPIPS, simula puertas CI y exporta salvaguardas.
Registrador de auditoría
Registra eventos de remediación en capas de imagen, metadatos y usuarios con trazas auditables exportables.
articles.related
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.
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.
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.
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.
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.
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.