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.

Render-blocking resources

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.

AtributoDescargaEjecuciónOrden garantizadoCuándo usarlo
(ninguno)BloqueaInmediataSíScripts muy pequeños y críticos (raro)
deferParalelaDespués del DOMSíLa mayoría de tus scripts propios que manipulan el DOM
asyncParalelaApenas descargaNoScripts 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>
El truco clásico: scripts antes de </body>

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.

Probá el Ejemplo 1: defer vs async vs normal

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>
ValorComportamiento
lazyPospone la carga hasta que el elemento esté cerca del viewport
eagerCarga inmediata (comportamiento normal, es el valor por defecto)
No uses lazy en imágenes visibles al cargar la página

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.

Probá el Ejemplo 2: Carga diferida de imágenes

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 asTipo de recurso
styleHojas de estilo CSS
scriptArchivos JavaScript
fontArchivos de fuentes (requiere crossorigin)
imageImágenes
fetchDatos 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">
relPropósitoPrioridadCuándo aplica
preconnectPreparar conexión de red a un dominioAltaRecursos de un dominio externo que se usarán pronto
preloadDescargar un recurso específico de esta páginaAltaRecursos críticos descubiertos tarde (fuentes en CSS, hero image)
prefetchAnticipar recursos de la próxima navegaciónBajaPáginas o scripts a los que el usuario probablemente navegue después
No abuses de preload

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.

Probá el Ejemplo 3: preload, preconnect y prefetch

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.

¿Vale la pena para todos los sitios?

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

CausaSolución
Imágenes sin dimensionesSiempre incluir width y height
Anuncios o embeds que cargan tardeReservar el espacio con CSS min-height antes de que carguen
Fuentes web que reemplazan la fuente de respaldoUsar font-display: swap y precargar la fuente con preload
Contenido inyectado dinámicamente arriba del existenteEvitar insertar contenido nuevo por encima de lo que el usuario ya está viendo
Probá el Ejemplo 4: Comparar con y sin reserva de espacio

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), defer es 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/async que 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