Prefetch y preload: adelantar sin bloquear
El lazy loading introduce una latencia en el momento del uso: cuando se necesita el chunk, hay que esperar su descarga. Prefetch y preload la borran adelantando esa descarga a un momento ocioso, sin bloquear el render. La diferencia entre rel=prefetch, rel=preload y rel=modulepreload, los hints que inyecta el bundler, la precarga por intención del router y las Speculation Rules de 2026.
El lazy loading tiene un precio y conviene nombrarlo: al diferir un chunk, cambias JavaScript inicial por una espera en el instante en que el usuario lo pide. Si el modal tarda 300 ms en descargarse tras el clic, has movido el coste, no lo has eliminado. La respuesta no es renunciar a diferir, sino adelantar la descarga a los huecos ociosos que hay antes del clic, cuando la red está libre y el usuario aún decide. Prefetch, preload y sus parientes son las herramientas para eso: traer el código pronto, en segundo plano, sin robarle prioridad a lo que la pantalla necesita ahora.
- Entender por qué el lazy loading traslada la latencia al momento del uso y cómo esconderla.
- Distinguir
rel=prefetch,rel=preloadyrel=modulepreloadpor prioridad y propósito. - Aprovechar los hints que el bundler inyecta y la precarga por intención del router.
- Conocer las Speculation Rules como la vía nativa del navegador para adelantar navegaciones.
Lazy sin latencia: adelantar la descarga
La clave está en separar dos preguntas que el import() estándar mezcla: cuándo se descarga un chunk y cuándo se ejecuta. Por defecto coinciden —el import() descarga y luego evalúa—, pero no tienen por qué. Puedes descargar el chunk mucho antes de ejecutarlo, guardándolo en la caché del navegador, de modo que cuando llegue el momento de usarlo la red ya no intervenga. Ese desacople es toda la idea: mantener la ejecución diferida, pero adelantar la descarga a un instante en que no molesta.
El instante ideal es un valle de ociosidad: el usuario lee, duda sobre un enlace, contempla la pantalla. En esos segundos la red no hace nada y el hilo tiene huecos. Adelantar ahí la descarga de lo que probablemente venga después es cobrar un tiempo muerto que, de otro modo, se perdería. Cuando la interacción llega, el trabajo ya está hecho y la navegación se siente instantánea.
El navegador ya tiene una noción de “tiempo ocioso” y la expone: requestIdleCallback ejecuta trabajo de baja prioridad en los huecos entre fotogramas, y las estrategias de prefetch se apoyan en esa misma idea de no competir jamás con lo urgente. Precargar bien es, en esencia, respetar esa cola: lo diferible viaja en la prioridad más baja, de modo que si aparece algo importante —una interacción, una imagen crítica— el prefetch cede el paso sin rechistar.
flowchart LR idle[El usuario lee y duda] --> pf[prefetch baja en tiempo ocioso] pf --> cache[El chunk espera en cache] click[Llega el clic] --> cache cache --> nav[Navegacion instantanea] style nav fill:#a6e3a1,color:#11111b
El navegador ofrece dos primitivas declarativas para pedir un recurso por adelantado, y elegir mal entre ellas es un error clásico, porque expresan intenciones opuestas: una es para el futuro incierto y otra para el presente urgente.
prefetch, preload y modulepreload
rel=prefetch dice “quizá necesite esto en una navegación futura”. El navegador lo descarga con la prioridad más baja, solo cuando la red está ociosa, y lo guarda en caché por si acaso. No compite con nada de la página actual; si nunca se usa, se pierde poco. Es la herramienta natural para adelantar el chunk de la ruta que el usuario probablemente visite después.
rel=preload dice lo contrario: “necesito esto ahora, en esta misma página, y el parser lo va a descubrir tarde”. Descarga con prioridad alta y sirve para recursos críticos que, por estar escondidos —una fuente referenciada desde un CSS, un chunk que el JavaScript pedirá enseguida—, el navegador no vería a tiempo. No es para diferir: es para adelantar lo urgente que de otro modo llegaría con retraso.
<!-- Futuro probable: baja en tiempo ocioso, prioridad baja, para otra navegacion -->
<link rel="prefetch" href="/assets/panel-Xy12.js" />
<!-- Necesario ya y descubierto tarde por el parser: prioridad alta -->
<link rel="preload" href="/fuentes/inter.woff2" as="font" crossorigin />
<!-- Modulo ESM: descarga mas parseo del modulo y de sus imports estaticos -->
<link rel="modulepreload" href="/assets/editor-Ab34.js" />
rel=modulepreload es la variante pensada para módulos ESM, y es la que de verdad importa para el lazy loading de JavaScript. Un preload corriente baja el archivo pero lo trata como un recurso opaco; modulepreload entiende que es un módulo, así que además de descargarlo lo parsea, lo compila y resuelve su cadena de imports estáticos, dejándolo listo para ejecutar sin más trabajo en el hilo. Para un chunk de tu aplicación, modulepreload adelanta no solo los bytes, sino también el coste de CPU que este nivel enseñó a temer.
prefetch
Prioridad mínima, para el futuro. Baja cuando la red descansa y no compite con la página actual. Ideal para rutas probables.
preload
Prioridad alta, para el presente. Adelanta un recurso crítico que el parser descubriría tarde. No es para diferir.
modulepreload
Como preload pero consciente de que es un módulo: descarga, parsea y resuelve sus imports. La opción correcta para chunks JS.
Precargar con prioridad alta demasiadas cosas es contraproducente: si todo es urgente, nada lo es, y el preload de un recurso secundario le roba ancho de banda al que de verdad bloquea la primera pintura. El preload es un bisturí para dos o tres recursos del camino crítico. Para adelantar lo diferible usa prefetch, que por diseño cede el paso a lo importante.
El navegador tiene la última palabra
Conviene recordar que todas estas etiquetas son pistas, no órdenes. Le dices al navegador qué te gustaría que priorizara, pero el planificador de recursos decide en última instancia y puede ignorarte si juzga que hay algo más urgente, si la memoria escasea o si el usuario pidió ahorrar datos. Un preload no garantiza que el recurso llegue el primero; sube su prioridad y poco más.
Esto no resta valor a la técnica, pero sí a la ilusión de control absoluto. La consecuencia práctica es que hay que medir el efecto de cada pista en lugar de asumirlo: mirar en el panel de red el orden y la prioridad reales con que se descargaron los recursos, y confirmar que la pista movió la cola en la dirección esperada. Cuando no lo hace, casi siempre es que competía con algo que el navegador consideró, con razón, más importante.
Hints del bundler y del router
Rara vez escribirás estas etiquetas a mano. Vite —sobre Rolldown en 2026— inyecta automáticamente un modulepreload para el chunk de entrada y sus imports estáticos, de modo que la cadena de módulos que la página necesita al arrancar se descarga en paralelo en lugar de en cascada. Es un comportamiento por defecto que resuelve gran parte del problema sin que hagas nada, y una de las razones por las que un build moderno arranca mejor que un empaquetado artesanal.
En herramientas más antiguas, estos hints se pedían con comentarios mágicos dentro del propio import() —anotaciones que el bundler leía para emitir una etiqueta de prefetch junto al chunk—. Vite prefiere resolverlo por convención y configuración antes que por comentarios, pero el concepto sobrevive: el bundler es quien conoce el mapa de chunks, así que es el lugar natural para decidir cuáles anunciar por adelantado y cuáles dejar para la demanda.
Para lo diferible, el trabajo lo hace el router, que sabe qué chunk corresponde a cada enlace y puede adelantarlo según una estrategia. Astro lo expone como una opción de configuración con estrategias por intención; Next lo hace por defecto en su componente de enlace; TanStack Router lo activa con una preferencia de precarga.
// astro.config: prefetch de enlaces segun estrategia de intencion
export default defineConfig({
prefetch: {
prefetchAll: true,
defaultStrategy: "hover", // tambien viewport, tap o load
},
});
Las estrategias son un abanico de agresividad. La de viewport precarga cuando el enlace entra en pantalla; la de hover, cuando el ratón lo toca; la de tap, en el instante entre que el dedo pulsa y suelta, que en móvil regala decenas de milisegundos gratis. Cada una apuesta a que la navegación ocurrirá; cuanto antes precargas, más aciertos borras de latencia pero más ancho de banda arriesgas en fallos. La de hover suele ser el punto dulce en escritorio, donde el ratón anticipa el clic con fiabilidad.
Una estrategia de prefetch mal configurada falla en silencio: no rompe nada, simplemente no adelanta. La forma de comprobarlo es abrir la pestaña de red, pasar el ratón sobre un enlace y ver aparecer la petición del chunk destino con prioridad baja, antes de cualquier clic. Si no aparece, la estrategia no está activa para ese enlace, y sigues pagando la latencia que creías haber escondido.
No solo código: datos y prioridad fina
Adelantar el JavaScript de una ruta es media victoria; la otra media son los datos que esa ruta renderiza. De poco sirve tener el chunk listo si al montar hay que esperar a la red por el contenido. Por eso los routers modernos precargan en paralelo el código y los datos del destino en cuanto conocen la intención, y por eso la carga de datos vive junto a la definición de la ruta: para descubrirla temprano y pedirla a la vez que el chunk.
Cuando necesites un control más fino sobre una petición concreta, el atributo fetchpriority te deja subir o bajar la prioridad de un recurso individual —una imagen high para el LCP, una petición secundaria low— sin recurrir a preload. Es el bisturí más pequeño de la caja, útil cuando el navegador prioriza distinto de como tú sabes que debería.
Speculation Rules: la vía nativa
La novedad que consolida todo esto en 2026 es la Speculation Rules API: un bloque declarativo, entendido por el navegador sin librería alguna, que describe qué URLs precargar o incluso prerenderizar por completo, y con qué avidez. Va un paso más allá que prefetch, porque no solo adelanta un recurso, sino que puede preparar una navegación entera —documento, subrecursos y hasta el render— en segundo plano, de modo que el clic se sienta instantáneo.
<script type="speculationrules">
{
"prerender": [
{ "where": { "href_matches": "/panel" }, "eagerness": "moderate" }
]
}
</script>
El campo de avidez —de conservative a eager— gradúa el mismo dilema de siempre entre acierto y desperdicio, ahora a escala de página completa. Un prerender acertado hace que la siguiente pantalla aparezca sin transición perceptible; uno fallido gasta CPU y datos en renderizar algo que nadie verá. La avidez es la perilla con la que calibras esa apuesta contra lo segura que sea tu predicción.
Hay un matiz de seguridad que no conviene olvidar: prerenderizar una página la ejecuta de verdad en segundo plano, con sus peticiones y sus efectos. Las URLs que prerenderizas deben ser seguras de visitar sin intención del usuario —nada de acciones destructivas por navegación— y baratas de preparar. Por eso las Speculation Rules se reservan para navegaciones muy probables y bien comportadas, no para rociar el sitio entero con prerenders especulativos que gastan recursos y pueden disparar efectos indeseados.
Adelantar el código sin adelantar los datos deja media latencia sin esconder: el chunk llega, monta, y entonces empieza la espera por lo que va a renderizar. Los routers modernos precargan ambos a la vez desde que conocen la intención, y algunos incluso montan con los datos ya en caché. La regla que resume el nivel: descubre pronto todo lo que la próxima pantalla necesita —código y datos— y pídelo junto, en tiempo ocioso y con prioridad baja.
Prefetch y preload parecen detalles de fontanería, pero encierran una de las ideas más elegantes del rendimiento web: el tiempo del usuario no es un flujo uniforme, sino una alternancia de ráfagas de atención y valles de indecisión, y esos valles son ancho de banda regalado. Mientras alguien lee un párrafo, pasa el ratón dudando sobre un enlace o simplemente contempla la pantalla antes de decidir, la red está ociosa y el hilo principal tiene huecos. Adelantar una descarga es cobrar ese tiempo muerto antes de que se pierda para siempre. Pero es, literalmente, una apuesta, y toda la técnica se reduce a gestionar bien esa apuesta. Precargas lo que crees que el usuario pedirá; si aciertas, la interacción se vuelve instantánea porque el trabajo ya estaba hecho; si fallas, has gastado datos y batería en algo que nadie usó. Por eso existe todo el gradiente de prioridades y avideces: prefetch es una apuesta tímida que solo juega con la calderilla del tiempo verdaderamente ocioso y cede ante cualquier cosa importante; el prerender agresivo es una apuesta fuerte que prepara una página entera confiando en una predicción firme. La maestría no está en precargarlo todo —eso reintroduce por la puerta de atrás el exceso de descarga que el lazy loading vino a eliminar— sino en calibrar la confianza de cada predicción contra su coste. Un enlace que el ratón ya toca es casi un clic: apuesta fuerte. Una ruta al fondo de un menú que casi nadie abre: no apuestes nada. Este es, además, el cierre lógico del lazy loading: dividir difiere el coste, y adelantar esconde la latencia que ese diferir creó, de modo que juntos te dan lo mejor de ambos mundos —un arranque ligero y una interacción sin esperas— siempre que respetes la única regla que sostiene el equilibrio: nunca dejes que la apuesta por el futuro estorbe a lo que el presente necesita.
- Difiere un chunk pesado y mide, en la pestaña de red, cuánto tarda su descarga desde el clic hasta que se ejecuta: esa es la latencia a esconder.
- Añade la precarga por hover del enlace o botón que lo dispara y confirma que el chunk aparece en la red antes del clic, con prioridad baja.
- Inspecciona el HTML que genera tu build y localiza los
modulepreloadque el bundler inyecta para los imports estáticos de la entrada. - Distingue en un caso real cuándo usarías
preloady cuándoprefetch, y justifica por qué intercambiarlos degradaría la página. - Declara un bloque de Speculation Rules para prerenderizar una ruta muy probable y compara la sensación del clic con y sin él.
- Sube la prioridad de la imagen del LCP con
fetchpriorityy verifica en el panel de red que adelanta su posición en la cola de descargas.