HTML y Performance
El HTML que escribís afecta directamente la velocidad de carga de tu página. Aprendé las técnicas nativas para que el navegador cargue lo importante primero y posponga lo que no es crítico.
Cómo el navegador procesa tu HTML
Antes de optimizar, conviene entender qué hace el navegador cuando recibe
tu HTML. Lee el documento de arriba hacia abajo, construyendo el
DOM (la estructura de la página) a medida que avanza.
Cuando encuentra una etiqueta <link> de CSS o un
<script>, por defecto se detiene a
descargar y procesar ese recurso antes de seguir leyendo el resto del HTML.
Esto significa que un <script> mal ubicado puede bloquear
por completo el renderizado de la página, dejando al usuario mirando una
pantalla en blanco mientras se descarga un archivo JavaScript que ni siquiera
necesita todavía. Entender este comportamiento es la base de casi todas las
técnicas de este tutorial.
Se le llama "recursos que bloquean el renderizado" a cualquier CSS o JavaScript que el navegador debe descargar y procesar antes de poder mostrar la página. Minimizar estos recursos es una de las formas más efectivas de mejorar la velocidad percibida de carga.
defer y async — cómo cargar scripts sin bloquear
Por defecto, cuando el navegador encuentra un <script>,
detiene todo: pausa la construcción del DOM, descarga el archivo y lo ejecuta
antes de seguir. Los atributos defer y async cambian
este comportamiento, pero de formas distintas y para casos distintos.
<!-- Comportamiento por defecto — bloquea el parseo del HTML -->
<script src="script.js"></script>
<!-- defer — descarga en paralelo, ejecuta después del DOM completo -->
<script src="script.js" defer></script>
<!-- async — descarga en paralelo, ejecuta apenas termina de descargar -->
<script src="script.js" async></script>
Con defer, el navegador descarga el script en segundo plano
mientras sigue construyendo el DOM normalmente. La ejecución se pospone
hasta que todo el HTML terminó de procesarse — justo antes del evento
DOMContentLoaded. Si hay varios scripts con defer,
se ejecutan en el orden en que aparecen en el documento, sin importar cuál
terminó de descargar primero.
Con async, el navegador también descarga en paralelo, pero
ejecuta el script apenas termina de descargar, interrumpiendo
momentáneamente el parseo del HTML para hacerlo. Esto significa que el orden
de ejecución entre varios scripts async no está garantizado —
el que termine de descargar primero, se ejecuta primero.
| Atributo | Descarga | Ejecución | Orden garantizado | Cuándo usarlo |
|---|---|---|---|---|
| (ninguno) | Bloquea | Inmediata | Sí | Scripts muy pequeños y críticos (raro) |
defer | Paralela | Después del DOM | Sí | La mayoría de tus scripts propios que manipulan el DOM |
async | Paralela | Apenas descarga | No | Scripts independientes sin relación con el DOM: analytics, ads, widgets de terceros |
<head>
<meta charset="UTF-8">
<title>Mi sitio</title>
<!-- CSS crítico — sí bloquea, pero es necesario para evitar FOUC -->
<link rel="stylesheet" href="estilos.css">
<!-- Analytics — no depende del DOM, no importa cuándo se ejecute -->
<script src="analytics.js" async></script>
<!-- Tu script principal — manipula el DOM, necesita orden garantizado -->
<script src="app.js" defer></script>
<!-- Un script que depende de app.js — el orden con defer se respeta -->
<script src="plugins.js" defer></script>
</head>
Una técnica anterior a defer era poner los scripts justo antes de </body>, para que el HTML ya estuviera renderizado cuando se ejecutaran. Hoy defer en el <head> logra lo mismo pero mejor: el navegador empieza a descargar el script inmediatamente en lugar de esperar a leer todo el HTML primero.
Carga diferida con loading="lazy"
Cuando una página tiene muchas imágenes — una galería, un feed largo, un artículo con varias fotos — descargar todas de inmediato desperdicia ancho de banda en imágenes que el usuario quizás nunca llegue a ver si no hace scroll hasta el final.
El atributo loading="lazy" le indica al navegador que postergue
la descarga de esa imagen (o iframe) hasta que esté a punto de entrar en
el viewport. Es una funcionalidad nativa del navegador — no requiere
ninguna librería de JavaScript.
<!-- Imágenes que aparecen "debajo del pliegue" (fuera de la vista inicial) -->
<img src="foto-1.jpg" alt="Producto 1" loading="lazy">
<img src="foto-2.jpg" alt="Producto 2" loading="lazy">
<img src="foto-3.jpg" alt="Producto 3" loading="lazy">
<!-- También funciona en iframes -->
<iframe src="https://www.youtube.com/embed/VIDEO_ID"
title="Video tutorial"
loading="lazy">
</iframe>
| Valor | Comportamiento |
|---|---|
lazy | Pospone la carga hasta que el elemento esté cerca del viewport |
eager | Carga inmediata (comportamiento normal, es el valor por defecto) |
La imagen principal de tu página — el hero, el logo, la primera foto visible sin hacer scroll — nunca debería tener loading="lazy". Diferir su carga retrasa el momento en que el usuario ve contenido útil, empeorando una métrica clave llamada LCP (Largest Contentful Paint). Para esa imagen, usá loading="eager" explícitamente o simplemente no pongas el atributo.
Pistas de recursos: preload, preconnect, prefetch
La etiqueta <link> con distintos valores de rel
le permite a tu HTML darle "pistas" al navegador sobre qué recursos va a
necesitar pronto, para que empiece a prepararse con anticipación. Son tres
herramientas con propósitos distintos y es importante no confundirlas.
preconnect — preparar la conexión a un dominio externo
Conectarse a otro dominio (DNS lookup, conexión TCP, handshake TLS) tiene
un costo de tiempo antes de poder descargar nada. preconnect
le dice al navegador que establezca esa conexión por adelantado, para
cuando el recurso real se necesite, la conexión ya esté lista.
<!-- Preparar la conexión antes de usar Google Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter" rel="stylesheet">
<!-- Preparar conexión a un CDN de imágenes -->
<link rel="preconnect" href="https://cdn.misitio.com">
preload — descargar un recurso crítico antes de tiempo
preload fuerza al navegador a descargar un recurso específico
con alta prioridad, sin esperar a encontrarlo durante el parseo normal del HTML
o el CSS. Es ideal para recursos que sabés con certeza que se van a necesitar
pronto pero que el navegador descubriría tarde — como una fuente web referenciada
desde el CSS, o la imagen principal de la página.
<!-- Precargar la fuente principal del sitio -->
<link rel="preload" href="/fonts/inter-bold.woff2"
as="font" type="font/woff2" crossorigin>
<!-- Precargar la imagen hero —se vería tarde si el navegador
la descubre solo después de parsear el CSS de fondo -->
<link rel="preload" href="/img/hero.jpg" as="image">
<!-- Precargar el CSS crítico -->
<link rel="preload" href="/css/critico.css" as="style">
El atributo as es obligatorio y le indica al navegador qué tipo
de recurso es, para que pueda asignarle la prioridad de descarga correcta y
aplicar las políticas de seguridad adecuadas:
| Valor de as | Tipo de recurso |
|---|---|
style | Hojas de estilo CSS |
script | Archivos JavaScript |
font | Archivos de fuentes (requiere crossorigin) |
image | Imágenes |
fetch | Datos obtenidos vía fetch o XHR |
prefetch — anticipar la próxima página
prefetch tiene un propósito diferente a los anteriores: en lugar
de optimizar la página actual, descarga recursos que probablemente se
necesiten en la próxima navegación del usuario, con baja
prioridad para no competir con los recursos de la página actual.
<!-- El usuario está en la home, probablemente vaya a "productos" después -->
<link rel="prefetch" href="/productos">
<!-- Anticipar el script de la siguiente sección del sitio -->
<link rel="prefetch" href="/js/checkout.js">
| rel | Propósito | Prioridad | Cuándo aplica |
|---|---|---|---|
preconnect | Preparar conexión de red a un dominio | Alta | Recursos de un dominio externo que se usarán pronto |
preload | Descargar un recurso específico de esta página | Alta | Recursos críticos descubiertos tarde (fuentes en CSS, hero image) |
prefetch | Anticipar recursos de la próxima navegación | Baja | Páginas o scripts a los que el usuario probablemente navegue después |
Cada recurso con preload compite por el ancho de banda disponible apenas arranca la carga de la página. Si marcás demasiados recursos como prioritarios, en la práctica ninguno lo es — terminás compitiendo contigo mismo y empeorando el tiempo de carga real. Usalo solo para 2 o 3 recursos verdaderamente críticos.
CSS crítico inline
Por defecto, todo el CSS de tu página bloquea el renderizado: el navegador no muestra nada hasta haber descargado y procesado completamente el archivo CSS enlazado. En archivos CSS grandes, esto puede demorar visiblemente la primera aparición de contenido en pantalla.
La técnica de CSS crítico consiste en identificar los estilos
mínimos necesarios para renderizar correctamente lo que el usuario ve sin
hacer scroll (el contenido "above the fold") e incluirlos directamente
dentro de una etiqueta <style> en el <head>.
El resto del CSS, menos urgente, se carga de forma asincrónica.
<head>
<meta charset="UTF-8">
<title>Mi sitio</title>
<!-- CSS crítico: inline, se aplica de inmediato sin esperar descargas -->
<style>
body { margin: 0; font-family: Arial, sans-serif; }
header { background: #264DE4; color: white; padding: 20px; }
.hero { min-height: 60vh; display: flex; align-items: center; }
.hero h1 { font-size: 2.5rem; }
/* ... solo lo necesario para el contenido visible inicial ... */
</style>
<!-- El resto del CSS se carga sin bloquear el renderizado -->
<link rel="stylesheet" href="estilos-completos.css"
media="print" onload="this.media='all'">
<!-- Fallback para navegadores sin JavaScript -->
<noscript>
<link rel="stylesheet" href="estilos-completos.css">
</noscript>
</head>
El truco de media="print" combinado con
onload="this.media='all'" funciona así: el navegador descarga
la hoja de estilos en segundo plano sin bloquear el renderizado porque,
al estar marcada para impresión, no aplica a la vista en pantalla. Una vez
que termina de cargar, el onload cambia el media a
all, aplicándola inmediatamente.
El CSS crítico inline agrega complejidad al proceso de build (generalmente requiere herramientas automáticas para extraerlo) y solo aporta una mejora notable en sitios con archivos CSS grandes o conexiones lentas. Para sitios pequeños con un CSS liviano, el beneficio es marginal frente a la complejidad que introduce.
Evitar saltos de layout (Cumulative Layout Shift)
Seguramente te pasó: estás por hacer clic en un botón y, justo antes, una imagen termina de cargar arriba y empuja todo el contenido hacia abajo — terminás haciendo clic en otra cosa. Eso es un layout shift, y Google lo mide como una de las métricas centrales de experiencia web (Core Web Vitals), bajo el nombre CLS.
La causa más común y más fácil de evitar es no reservar espacio para imágenes antes de que carguen. Cuando el navegador no sabe las dimensiones de una imagen, inicialmente le asigna un espacio de altura cero, y cuando la imagen finalmente carga, todo el contenido de abajo se desplaza para hacerle lugar.
<!-- ❌ Mal — sin width/height, el navegador no reserva espacio -->
<img src="foto.jpg" alt="Producto">
<!-- ✅ Bien — el navegador reserva el espacio correcto desde el inicio -->
<img src="foto.jpg" alt="Producto" width="800" height="600">
Es importante notar que estos atributos width y height
no fuerzan ese tamaño visual si tenés CSS que diga lo contrario (por ejemplo
width: 100% para hacerla responsiva) — sirven para que el navegador
calcule la proporción (aspect ratio) y reserve el espacio
correcto incluso antes de que la imagen termine de descargar.
aspect-ratio como complemento moderno
<style>
img {
width: 100%;
height: auto;
/* El navegador calcula la altura correcta usando los atributos
width/height del HTML como proporción, incluso antes de cargar */
}
</style>
<img src="foto.jpg" alt="Producto" width="800" height="600">
<!-- Con CSS width:100%, la imagen se hace responsiva manteniendo
la proporción 800:600 reservada desde el primer instante -->
Otras causas comunes de layout shift
| Causa | Solución |
|---|---|
| Imágenes sin dimensiones | Siempre incluir width y height |
| Anuncios o embeds que cargan tarde | Reservar el espacio con CSS min-height antes de que carguen |
| Fuentes web que reemplazan la fuente de respaldo | Usar font-display: swap y precargar la fuente con preload |
| Contenido inyectado dinámicamente arriba del existente | Evitar insertar contenido nuevo por encima de lo que el usuario ya está viendo |
Buenas Prácticas
- defer por defecto en tus scripts: a menos que tengas una razón específica para usar
async(scripts de terceros independientes) o ningún atributo (casos muy puntuales),deferes la opción correcta para la mayoría del JavaScript propio - loading="lazy" en todo lo que esté fuera de la vista inicial: pero nunca en la imagen principal visible al cargar
- preconnect para dominios externos críticos: fuentes de Google, CDNs, APIs que se usan apenas carga la página — pero no abuses, cada preconnect tiene un costo
- preload solo para 2-3 recursos verdaderamente críticos: la fuente principal, la imagen hero — usarlo de más le quita efectividad a todos
- Siempre width y height en imágenes: es la forma más simple y efectiva de evitar layout shifts, incluso si después las hacés responsivas con CSS
- Minimizá los render-blocking resources: cada CSS y script sin
defer/asyncque el navegador encuentra en el<head>retrasa la primera pintura de la página - Medí antes de optimizar: usá Lighthouse o PageSpeed Insights para identificar qué realmente está afectando tu sitio antes de aplicar técnicas avanzadas como CSS crítico inline