La dinámica de estar en fila: si uno se demora, demora al resto
¿Se ha enojado usted al soportar la demora de la persona delante suyo en una extensa fila, al pagar un servicio o comprar un artículo?

Estando en una fila debemos avanzar uno a uno. Pero una larga demora retrasaría al resto, generando impaciencia.
En sitios web esa impaciencia se traduce en la demora de la carga de los elementos de una página al ingresarlos a un análisis previo. En el dibujo de arriba, cada persona representa un recurso web, y el análisis de cada recurso es el ingreso a la puerta.
En desarrollo web esa dinámica es conocida como critical rendering path o ruta de renderizado crítica. En los siguientes párrafos explico ese útil concepto en desarrollo web.
En una avenida de un solo carril, es mejor un vehículo a la vez
En nuestra vida cotidiana es posible que hayamos afrontado la necesidad de repetir una o más tareas, cada hora o todos los días: tender la cama luego de despertar o calentar la hornilla para preparar desayuno, día tras día. Si algo bloquea o demora el proceso de cada tarea, cada mañana sería un caos.
Ese desafío es similar a una avenida con un solo carril de ida. Cada vehículo, liviano o pesado, deberá respetar su turno para que el flujo se mantenga ágil. Un pesado camión inmóvil crearía una demora infinita a los vehículos que, impacientes, esperan detrás. Esa dinámica también ocurre al ejecutar archivos y código que representan una página web.
La carga módulo por módulo, respetando dependencias (códigos principales y secundarios)
El desafío de la carga inmediata o diferida
En un sitio web no todos los archivos optarán (o deberán) ser cargados de inmediato. Eso dependerá del orden deseado de presentación, de su estructura, diseño, funcionalidad o de todo lo anterior.
Sin embargo, descuidar ese orden creará problemas de lentitud en la presentación de los elementos, o esfuerzos en procesos innecesarios para elementos que no requieren ser activados de inmediato, o falta de carga en elementos que sí se necesitan ser activados con prontitud.
En desarrollo web se requiere que todos los códigos (o cualquier otro recurso, como imágenes, videos o tipografías) se carguen (es decir, que los códigos sean interpretados y luego activados por el navegador web) uno a uno y en orden; o lo que es lo mismo, en módulos que permiten separar archivos JS independientes. Eso facilita que cada módulo de código deberá cargarse y ejecutarse progresivamente, o en el momento útil, o esperar hasta que un código principal haya sido cargado o ejecutado primero, y luego sus códigos secundarios (dependencias), etcétera.
Si esas cargas son desordenadas o no están correctamente organizadas, afectaría el rendimiento (la carga rápida) en tiempo y en funciones en una página web.

En la imagen superior se resume de forma visual el concepto de carga de archivos para un sitio web. El visto azul refiere a que una vez cargados han sido activados. Los cuadrados representan un archivo base o principal, y los rombos son los archivos que contienen dependencias. Esas dependencias pueden ser funciones que deben ser activadas una vez que los datos del archivo principal esten cargados (y no al revés, como si deseáramos colocarnos en el pie el calcetín luego de habernos calzado el zapato). Luego, habrán archivos que deban ser cargados en paralelo (como en las dependencias a2 y a3); o aquellos que deban ser cargados en secuencia (como las dependencias b2 y b3).
Finalmente, el concepto de precarga o el de retrasar la carga de archivos es aquí importante de revisar. Por ejemplo, en la imagen superior los círculos negros representan a aquel código (o recurso, como una o más imágenes) que ha sido cargado pero aún no ha sido mostrado o activado.
Como veremos, todos esos criterios corresponden a indicar la prioridad en la carga inmediata o demorada de archivos o recursos, todo eso para acelerar la presentación de los contenido web y mejorar la experiencia de usuario. El concepto técnico que agrupa esos criterios es la Gestión de carga de recursos (Resource Load Management).
Activación de comportamiento de carga (inmediata o retrasada) para recursos web
Los lenguajes web HTML, CSS y JS proveen, cada uno, de adecuadas sintaxis aplicadas para retrasar, precargar o activar carga paralela de archivos y recursos. A estas técnicas prefiero llamarlas estrategias de carga de recursos:
- Para diferir recursos de Javascript: atributos desde HTML: defer | async
- Para precargar recursos CSS, JS y otros: atributos desde HTML rel="preload" | rel="modulepreload"
- Para activar según condición de uso de recursos (imágenes, tipografías): atributos HTML lazy
- Para precargar y posactivar recursos atributos HTML rel=prefetch
Antes de analizar cada estrategia de carga de recursos, repasemos cómo es el comportamiento estándar de una página web promedio.
¿Cómo cargan los archivos de una página web?
Una vez accedido al vínculo o dirección web que deseamos, se han generado "ocho viajes de ida y vuelta [conexiones de datos] al servidor [que contiene la web]" antes de iniciar la solicitud interna entre nuestro navegador y la página (developer.mozilla.org, 2025).
Así como en un solo carril para varios vehículos en donde debe pasar uno a la vez, un navegador web procesa en un primer hilo (un monohilo) de procesos las siguientes tareas en secuencia. Cada proceso debe iniciar y finalizar para poder iniciar el siguiente en fila. A todo este proceso se lo llama Critical rendering path o Ruta de renderizado crítica.
- Interpretación de sintaxis HTML y construcción del DOM
- Apuntado a CSS y disponibilidad de ejecución CSS desde JS
- Ejecución de scripts (según posición y atributos)
- Creación de elementos CSS
- Dibujo de elementos en tiempo real
- Muestra de diseño de elementos
- Composición final

El gráfico anterior puede parecer atiborrado, pero el objetivo es mostrar los posibles comportamientos y bloqueos necesarios de estilos visuales al cargar recursos web. Un bloqueo necesario es, por ejemplo, el que ocurre cuando se están cargando los estilos CSS. Si estos no se cargan (o demorar en cargar a causa de una cantidad excesiva de estilos o de una conexión lenta) el navegador no podrá tomar decisiones de ubicación, color, tipografía o tamaños. Tan solo mostrará los elementos HTML al desnudo, sin elucubrar estilos. Mucho más si esos estilos se replantean a causa de scripts de Javascript. El navegador no puede adivinar o usar información de estilos incompleta.
Es por eso que existen las estrategias de carga para justamente los recursos web dados en CSS o JS, y así minimizar los bloqueos potenciales cuando no se los ha indicado como importantes.
Estrategias de carga para recursos mediante atributos defer o async
El comportamiento estándar de los códigos y recursos (como los estilos CSS) son los siguientes:
- Los estilos CSS son leídos desde arriba hacia abajo por el analizador del navegador
- Los estilos CSS bloquean temporalmente el análisis del HTML, su visualización y la actualización visual del resto de elementos
- Si el desarrollador lo ha programado así, los valores de las reglas CSS tienen la capacidad de sobreescribirse (es decir, de actualizar sus valores) a medida que se va generando el análisis del código CSS desde el navegador.
- Cada reajuste de valores en CSS afecta la composición final de los elementos web a presentar, alterando la medición del Largest Contentful Paint, que es el tiempo de demora del elemento más grande (o más extenso, como un texto) hasta que es mostrado en la página web (el tiempo promedio ideal no debería exceder los 2.5 segundos).
En la mayoría de las sintaxis de lenguajes para desarrollo web, la última actualización de datos es la que se impone (o sobreescribe) a las anteriores (ver ejemplo abajo). Es por eso que los recursos CSS deben descargarse por completo para que el análisis del navegador tenga certezas:
En CSS
.elemento{width: 200px;}
/* ... muchas reglas intermedias ... */
.elemento{width: 400px;}
/* ... muchas reglas intermedias ... */
.elemento{width: 50px;}
En resumen, la carga de los recursos CSS bloqueará la carga HTML (sin ningún indicador de demora de carga). Es por eso que ahora revisaremos estrategias de diferimiento de recursos (como el CSS)
Estrategia de carga para recursos web (como los estilos CSS): atributo rel="preload"
Esta técnica es aplicable para recursos web varios, como tipografías, imágenes o scripts de Javascript. Pero usaremos como ejemplo a recursos CSS en los siguientes ejemplos.
El atributo rel="preload"
precarga el recurso CSS NO CRÍTICO, sin los bloqueos naturales que ocasiona la carga de CSS, y lo mantiene preparado para luego aplicarlo en forma de estilo visual a uno o más elementos.
Un recurso CSS no crítico es todo aquel conjunto de estilos accesorios, no esenciales de inmediato en un diseño web. Por ejemplo, los estilos de una sección al final de la página web, o los estilos CSS que serán visualizados únicamente al crear varios deslizamiento hacia abajo. A diferencia de los estilos críticos que sí serán mostrados apenas aparecer la página. Estos estilos críticos suelen ser invocados sin ningún tipo de diferimiento como mediaquery o preload.
Otros contenidos también pueden ser precargados al ser indicados en el atributo <link rel="preload" as="AQUI_ATRIBUTO"...
-
as="font"
..para tipografías -
as="image"
..para imágenes -
as="script"
..para código Javascript -
as="style"
..para estilos CSS -
as="media"
..para videos o audios
La sintaxis correcta para apuntar a un recurso CSS que se debe precargar es:
En HTML
<head>
<!-- Todos los archivos CSS
estan ubicados en la misma carpeta raíz -->
<!-- Cargar estilo Critico y bloqueante -->
<link rel="stylesheet" href="critico.css">
<!-- Precargar CSS sin bloquear -->
<link rel="preload" href="css-no-critico.css" as="style"
onload="this.onload=null;this.rel='stylesheet'">
<!--
4. Cuando termina la descarga del recurso indicado como precarga:
- Se dispara el evento `onload`.
- Se ejecuta: `this.rel = 'stylesheet'`.
5. En ese momento:
- El navegador aplica el archivo como hoja de estilos.
- Si hay reglas CSS que afectan el layout, se recalcula y repinta.
-->
<!-- Para CSS en navegadores sin JS -->
<noscript>
<link rel="stylesheet" href="css-no-critico.css">
</noscript>
</head>
Estrategia (sin precarga) de aplicación de recursos CSS mediante condición de JS
Aquí se establece que, luego de haber finalizado el evento load
, el recurso CSS sea aplicado. Ojo: el recurso CSS no se ha precargado antes:
En HTML
<head>
<!-- CSS crítico cargado normalmente -->
<link rel="stylesheet" href="critico.css">
</head>
<body>
<p>Hola Guayaquil</p>
<script>
// Carga el CSS no crítico solo después de que toda la página ha cargado
window.addEventListener('load', function () {
var link = document.createElement('link');
link.rel = 'stylesheet';
// Nombre de estilo NO CRÍTICO
link.href = 'css-no-critico.css';
document.head.appendChild(link);
});
</script>
</body>
Estrategia de carga de recursos con el atributo rel= "modulepreload"
Desde septiembre de 2023 esta técnica está disponible en navegadores modernos. Revise aquí y en tiempo actualizado la disponibilidad de rel=modulepreload
en cada navegador web.
rel="modulepreload"
activa una precarga de módulos JS en forma paralela (pero que todavía no ejecuta), junto a sus dependencias (es decir, junto a otros módulos que deberían ser ejecutados posteriormente, en cadena).
La diferencia con rel=preload
es que este descarga el recurso (en el caché de memoria) y lo prepara, pero no lo ejecuta hasta que esa orden sea explícita en el código. Mientras que rel=modulepreload"
obtiene el módulo, lo analiza y compila, y [dispone los módulos] listos para ejecutarse" al ser invocados desde una línea de código explícita dentro de alguno de los módulos apuntados mediante la función import()
Mdn web docs (2025a).
A modo de concepto, lo que se hará en los siguientes códigos será indicar un elemento que reciba algunas alteraciones de estilo desde dos módulos de scripts (códigos en javascript).

rel="modulepreload"
apunta al módulo de scripts a precargar. Luego, en algún momento a lo largo del HTML se invoca mediante el atributo type="module"
al módulo principal que a su vez contiene la activación del código ya antes precargado. duduromeroa.comPRIMERO DESDE HTML
<head>
<!-- Estilo inicial cargado por default -->
<!-- Estilos CSS críticos, que deben ser activados ya -->
<style>
.container {
padding: 20px;
font-size: 1.4rem;
}
button {
padding: 10px;
}
</style>
<!-- modulepreload:
- Precarga del modulo JS que contiene el comportamiento hover
- Este módulo AÚN no será activado...
- ...solo alojado en el caché de navegador
-->
<link rel="modulepreload" href="estilo-hover.js">
</head>
<body>
<div id="cajita" class="container">
<button id="hoverBtn">De click aqui</button>
</div>
<!-- SOLO Y ÚNICAMENTE AQUÍ se invoca al módulo principal
- El atributo type="module"
- No bloquea el renderizado inicial
- Soporta import/export en el script apuntado
- Ese script tiene su propio ámbito de ejecución
- Al usarlo se sobreentiende que estamos usando
funciones import() para invocar módulos JS
-->
<script type="module" src="principal.js"></script>
</body>
En el código anterior primero se indican los estilos CSS que serán ejecutados de inmediato al estar insertados dentro del elemento <head>
Luego, en el elemento <body> se establece el elemento botón que recibirá los estilos CSS iniciales pero que también recibirá los estilos que solamente estarán precargados, pero que en algún momento se indicarán que sean aplicados.
Finalmente (seguimos en el código anterior) se muesta el elemento <script> que apunta a invocar al módulo de JS que contiene la orden de ejecución de más estilos CSS, pero desde ese módulo de JS.
En los siguientes ejemplos se muestra el contenido de los módulos, los estilos a aplicarse y la indicación explícita que ejecuta esa aplicación de estilos mediante la función import()
.
Desde módulo principal.js
// AQUI SE ACTIVA EL MODULO JS ANTES PRECARGADO
// ¿Por qué se activa ahora y aquí?
// Por el import() que está al final del btn.addEventListener
// Selección de elementos
const alojaCajita = document.getElementById('cajita');
const btn = document.getElementById('hoverBtn');
// Estilo base aplicado desde JS
alojaCajita.style.backgroundColor = '#e0e0e0';
// Lógica dinámica: al pasar el cursor por el botón
// Ojo con la función asyn()
btn.addEventListener('mouseover', async () => {
// AQUÍ SE EJECUTA EL MÓDULO PRE-CARGADO
/* Simbolo ./
..."Busca .js en la misma carpeta donde está
este archivo JavaScript que ejecuta el import." */
const hover = await import('./estilo-hover.js');
// La función aplicarHover(btn)
hover.aplicarHover(btn);
});
Desde módulo estilo-hover.js
/* rel="modulepreload" asegura
que el módulo estilo-hover.js no bloquee nada,
pero que sí esté listo
en memoria para el import(). */
export function aplicarHover(boton) {
// Estilo durante hover de cursor
boton.style.backgroundColor = '#4CAF50';
boton.style.color = 'white';
// Reversión cuando el cursor se va
boton.addEventListener('mouseout', () => {
// Sin valores
boton.style.backgroundColor = '';
// Sin valores
boton.style.color = '';
});
}
Estategia para carga de scripts desde atributo defer
Al agregar el atributo defer
a un elemento de invocación de script externo en la forma <script>...</script>
la carga de ese script se hará únicamente cuando todo el resto de los códigos HTML sean analizados y cargados por el navegador web.
La carga de código Javascript (en una web y sin el atributo defer
) activa su comportamiento natural. Esto es, que todas las sintaxis de Javascript son cargadas luego del análisis del HTML y de la estructura de nodos de estilos CSS (ver arriba imagen Explicación de ruta de renderizado crítica).
Tener muy en cuenta las diferencias entre descargar y ejecutar. En otras palabras, con el atributo defer
un script:
- Se descarga en paralelo al flujo lineal y secuencial del renderizado crítico estándar, sin bloquearlo.
- Se ejecuta luego de que ese renderizado finalice.
- Ojo con esto: defer no ejecuta en paralelo. Lo que ocurre en paralelo es la descarga del archivo, no su ejecución.
En HTML
<html>
<head>
<!-- SIN DEFER: Se ejecuta de inmediato luego del HTML -->
<script src="direccion.js"></script>
<!-- CON DEFER: Sólo cuando el HTML ha terminado su análisis
desde el navegador -->
<!-- Se ejecuta este script -->
<script src="nombres.js" defer></script>
<!-- Luego este... -->
<script src="fechas.js" defer></script>
</head>
</html>
Estategia para carga de scripts desde atributo async
Con ese atributo "pegado" a una invocación de script desde HTML, descarga el script en paralelo al render crítico estándart y ejecuta el script tan pronto pueda, incluso si el DOM (la estructura de nodos HTML) aún no ha terminado de ser analizada.
Sin embargo, async
no garantiza la ejecución de muchos scripts async en orden de aparición, ya que esa activación dependerá de cuánto tiempo demore cada script en descargase al caché del navegador. Por lo que la estrategia con async
funciona mejor con scripts no dependientes de otros (autónomos), comúmnente con códigos de análisis de desempeño o de servicios de estadísticas de uso.
- Se descarga en paralelo...
- Se ejecuta cuando haya sido descargado, aunque puede interrumpir el análisis del HTML.
- Ojo con esto: Si el script contiene dependencias enlazadas, eso podría demorar la activación (no la carga, pues esa ocurrre paralelo). O a su vez, si es un script ligero, pero con dependencias enlazadas que demoran en cargar, el primero quedaría solitario, sin dependencias que lo complementen.
En HTML
<html>
<head>
<script async src="https://data.com/count/24"></script>
</head>
</html>
Estrategia de descarga de imágenes con atributo lazy
Indica al navegador que la descarga de la imagen (y su visualización) será posible solo cuando el contenedor de la imagen esté cercana al contenido visible del navegador, ya sea por deslizamiento o por ubicación visible en la pantalla.
Recordemos que las imágenes no alteran el ciclo de renderizado crítico del navegador. Por lo que la estrategia con loading="lazy"
es únicamente para aligerar la recarga inicial (o la carga en el ancho de banda) del hilo principal y otros recursos web.
Sin embargo, no es una estrategia precisa cuando se use contenido crítico, debido a que puede haber un retraso según otras prioridades en tiempo real del navegador.
En CSS
<!-- Llamado a imagen a descargar... -->
<img src="foto.jpg" alt="Descripción bla bla" loading="lazy">
<!-- Solo cuando el contenedor de la imagen
esté cercano al encuadre
visible al navegador -->
Estrategia de descarga de recursos con atributo rel="prefetch"
El verbo inglés fetch refiere a obtener o atraer alguna cosa hacia uno. En sintaxis de desarrollo web el atributo rel="prefetch"
indica que cierto recurso será descargado con mucha anterioridad en la memoria fugaz pero inmediata del caché del navegador, hasta que ese recurso (sea un código HTML o algún otro) sea o no accedido por el usuario de la web.
rel="prefetch"
tendría el mismo comportamiento como si el usuario cliqueara en un vínculo para acceder a una nueva página; excepto que esta vez no la muestra, sino que la aloja en memoria y de inmediato la activa cuando el usuario acceda a ese recurso.
Con rel="prefetch"
la descarga de ese recurso se realizará en baja prioridad, sin tocar el hilo principal. Por lo tanto, al ser preobtenido en el caché esa carga será mucho más rápida cuando el usuario desee obtenerla, ya sea mediante un click de un vínculo o alguna otra interacción.
Sin embargo, exceder en su uso podría rellenar el caché del navegador con múltiples archivos que posiblemente el usuario nunca ha accedido (después de usar el sitio web). En otros casos, rel="prefetch"
no será tomado en cuenta cuando el navegador esté en modo de ahorro de datos.
Por otro lado, rel="prefetch"
será afectado por las políticas de caché del navegador. Si el caché es limpiado manualmente, o si el recurso expira (por ejemplo, desde el servidor con .htaccess
o headers HTTP), entonces ese recurso se eliminará, y deberá volver a descargarse si se solicita más adelante.
Ojo con la ubicación de las invocaciones de recursos con rel="prefetch"
Dentro del elemento
la carga de recursos (y de las estrategias de diferimiento) será de inmediato. Pero fuera de ese elemento y dentro de otros, como <body> la preobtención medianterel="prefetch"
podría no ser de inmediato, pues hasta ese punto del código otros varios recursos pueden competir la activación según un adecuado o no ancho de banda.
En HTML
<!-- En el caso de que ese recurso HTML esté ubicado
en la misma carpeta del código que contiene el prefecth -->
<head>
<link rel="prefetch" href="productos.html">
</head>
Ojo con las estrategias de interacción o de acceso de usuario a los recursos web de la página
¿Por qué entonces es necesario preobtener (pre alojarlo en caché) un recurso que NO sabremos si será o no accedido por el usuario? La respuesta a eso debe ser fundamentada desde nuestras estrategias de interacción, organizadas según nuestra estructura de elementos, o de información, o de utilidad para nuestros usuarios web.
Según los principios de diseño de experiencia de usuario de Donald A. Norman, la visibilidad de los elementos, las respuestas sin demora de las interacciones, la coherencia y otras acciones construyen comodidad y facilidad de exploración del usuario en el uso de sistemas interactivos, como una página web.
Otro principio válido, como el de flexibilidad y eficiencia de uso (principio 7, de Jakob Nielsen) propone que se le deben ofrecer al usuario sencillos atajos que permitan una fácil navegación, sin perderse en una laberinto de vínculos y botones sin sentido. Por lo tanto, aplicar estrategias de precarga de recursos como los revisados a lo largo de este artículo (y especialmente de rel="prefetch"
) debe ser fundamentado bajo todos los criterios arriba expuestos, que buscan facilidad y utilidad a favor del usuario universal, es decir, para todas las personas que usen nuestros productos tecnológicos.
Aclarando confusiones
-
<link rel="stylesheet" href="estilos.css" defer>
Error!. Atributos defer o async es solo para JS. Lo correcto es <script src="nombres.js" defer></script> -
<link rel="stylesheet" href="colores.css" prefetch>
Error!. Es un valor de atributo. Lo correcto es rel="prefetch" -
<img src="img.jpg" lazy>
Error!. Es un valor de atributo. Lo correcto es loading="lazy"