wandres.dev
ONTOLOGÍA · El mapa del rendimiento web

La cadena de causas: del clic al píxel

Los diez eslabones que separan el momento en que el usuario pulsa un enlace del momento en que ve contenido, y cuánto cuesta cada uno en condiciones reales.

⏱ 18 min

Entre el clic y el píxel hay una cadena de unos diez eslabones, cada uno con su coste y su causa de lentitud propia. Casi todo el diagnóstico de rendimiento consiste en localizar en qué eslabón se pierde el tiempo, porque las herramientas te dan un total y el total no dice nada. Esta lección recorre la cadena entera con cifras de referencia para que sepas qué es normal y qué es una anomalía.

🎯 Al terminar esta lección sabrás
  • Enumerar los eslabones de una carga de página en orden y saber qué hace cada uno.
  • Estimar el coste típico de cada eslabón en una red móvil realista.
  • Identificar qué eslabones son secuenciales y cuáles se solapan.
  • Localizar en qué eslabón está el cuello de botella a partir de un desglose de tiempos.

Los diez eslabones

La cadena es la misma para cualquier página; lo que cambia es la duración de cada tramo. Las cifras que doy son órdenes de magnitud para una conexión móvil de calidad media, del tipo que Lighthouse emula por defecto: 150 ms de latencia y 1,6 Mbps de bajada.

1. Resolución de DNS. Traducir el nombre del dominio a una dirección IP. Si está en la caché del sistema operativo o del navegador, es gratis. Si no, es al menos un viaje de ida y vuelta hasta el resolutor, y puede ser varios si hay que recorrer la jerarquía. Coste típico en frío: 20-120 ms.

2. Apertura de la conexión TCP. El saludo de tres pasos. Un viaje de ida y vuelta completo antes de poder enviar un solo byte de aplicación. Coste: exactamente 1 RTT, unos 150 ms en nuestra red de referencia.

3. Negociación TLS. Con TLS 1.3, un viaje de ida y vuelta adicional en una conexión nueva. Con TLS 1.2 eran dos. Coste: 1 RTT más, otros 150 ms.

4. Petición y espera del primer byte. Envías la petición HTTP, el servidor la procesa y devuelve. El viaje son otros 150 ms, más el tiempo de proceso del servidor. Todo lo acumulado hasta aquí es lo que mide el TTFB: en una primera visita a un dominio nuevo, difícilmente baja de 450-600 ms aunque tu backend responda instantáneamente.

5. Transferencia del HTML. No llega de golpe. TCP arranca con una ventana de congestión pequeña, del orden de diez segmentos, lo que significa que en el primer envío caben unos 14 KB de datos. Todo lo que exceda esa cifra necesita un viaje de ida y vuelta adicional para ampliar la ventana. Un HTML de 60 KB comprimidos son varios viajes solo de transferencia.

6. Parseo del HTML y construcción del DOM. El parser va de arriba abajo. Cuando encuentra una hoja de estilos, sigue parseando pero no podrá pintar; cuando encuentra un script síncrono, se detiene en seco. Aquí es donde empiezan los problemas de la ruta crítica.

7. Descubrimiento y descarga de subrecursos. El navegador lanza peticiones para CSS, JavaScript, fuentes e imágenes. Cada dominio nuevo repite los eslabones 1 a 3. Un sitio con recursos en cinco dominios distintos paga cinco veces DNS, TCP y TLS.

8. Construcción del CSSOM. Todo el CSS bloqueante tiene que llegar y parsearse antes de poder calcular estilos. Aquí es donde se pierde tiempo si tienes una hoja de estilos monolítica de 300 KB.

9. Ejecución de JavaScript. Descargar es solo el principio: hay que parsear, compilar y ejecutar, y todo eso ocurre en el hilo principal. Este eslabón es el que más varía con el dispositivo: el mismo bundle puede costar un factor de diez más en un teléfono de gama media que en un portátil.

10. Layout, paint y composición. Calcular la geometría de cada caja, rasterizar y componer las capas. En una página normal son decenas de milisegundos; en una con un DOM de veinte mil nodos, cientos.

⚠️
Los eslabones 1 a 4 se pagan íntegros antes de que exista una sola línea de tu código

Es habitual que un equipo dedique semanas a recortar 80 KB del bundle mientras el TTFB del percentil 75 está en 1,4 s. Esos 1,4 s ocurren antes de que el navegador vea el primer byte de HTML: ninguna optimización de frontend los toca. La palanca ahí es reducir viajes de ida y vuelta, servir desde el borde y eliminar redirecciones.

Qué es secuencial y qué se solapa

La distinción entre lo que ocurre en serie y lo que ocurre en paralelo es la que decide dónde merece la pena invertir.

Los eslabones 1 a 4 son estrictamente secuenciales. No puedes negociar TLS antes de tener conexión TCP, ni conectar antes de resolver el nombre. Su coste se suma, y cada uno de ellos es aproximadamente un RTT. Por eso reducir el RTT tiene un efecto multiplicador: dividir la latencia entre dos divide entre dos ese bloque entero.

Los eslabones 5 a 7 se solapan parcialmente. El navegador parsea el HTML mientras lo recibe, y un componente llamado preload scanner va por delante del parser buscando URLs para empezar a descargarlas antes de que el parser llegue a ellas. Ese solapamiento es lo que hace que la web sea usable, y también lo que se rompe cuando escondes las URLs detrás de JavaScript.

Los eslabones 8 a 10 son serializados por el hilo principal. Solo hay uno, y todo compite por él: ejecutar scripts, calcular estilos, hacer layout. De ahí que la fase de interacción sea un problema de cola, no de ancho de banda.

Una consecuencia poco intuitiva de esta estructura: añadir un dominio nuevo cuesta caro aunque el recurso sea pequeño. Una fuente de 15 KB alojada en un dominio de terceros paga DNS, TCP y TLS antes de transferir sus 15 KB. En la red de referencia eso son unos 450 ms de sobrecoste para 15 KB de contenido. Servir esa misma fuente desde tu propio origen, reutilizando la conexión ya abierta, la hace prácticamente gratis.

Leer un desglose de tiempos

Cuando tienes un desglose por eslabón, el diagnóstico es casi mecánico. Estos son los patrones que se repiten.

Síntoma en los datos Eslabón sospechoso Qué mirar
TTFB alto, resto normal 1 a 4 Redirecciones, distancia al servidor, fallo de caché en el borde, tiempo de proceso del backend
TTFB bajo, FCP muy posterior 6 y 8 CSS bloqueante grande, scripts síncronos en el head
FCP bueno, LCP muy posterior 7 El recurso del LCP se descubre tarde o compite con otros
Todo bien pero el INP malo 9 JavaScript que monopoliza el hilo principal
Diferencia enorme entre gama alta y gama media 9 y 10 Coste de CPU, no de red
Muchos dominios en la cascada 1 a 3 repetidos Terceros; cada origen cuesta tres viajes de ida y vuelta

El error clásico al leer una cascada es fijarse en la barra más larga. La barra más larga suele ser un recurso grande que se descarga en paralelo y no bloquea nada. Lo que importa es la cadena más larga de dependencias: qué recurso no pudo empezar hasta que otro terminó. Un CSS de 12 KB que tarda 200 ms puede costar más al LCP que una imagen de 400 KB que tarda 900 ms, si el CSS bloquea el pintado y la imagen no.

El eslabón que casi nadie mira es el cero: la decisión de navegar

La cadena que acabo de describir empieza cuando el navegador recibe la orden de navegar, pero hay un tramo anterior que no aparece en ninguna herramienta y que a veces domina el total: el tiempo entre que el usuario toca el enlace y el navegador empieza a resolver el DNS. Si esa página de origen tiene el hilo principal bloqueado con un long task de 400 ms, el clic se encola y la navegación arranca 400 ms tarde. La víctima es la página de destino, cuyo LCP crece por culpa de código que no es suyo, y el responsable es la página de salida. Lo he visto arruinar el rendimiento de un checkout entero: el problema no estaba en el checkout, estaba en la ficha de producto anterior, que tenía un script de analítica que bloqueaba el hilo justo cuando la gente pulsaba comprar. Cuando el LCP de una página parece inexplicablemente malo y su TTFB es alto sin razón de servidor, sospecha del unload de la página anterior.

Un ejemplo numérico completo

Vale la pena hacer el cálculo una vez a mano para que las proporciones se queden grabadas. Página sencilla, primera visita, red de 150 ms de RTT y 1,6 Mbps, con el HTML en tu origen y una fuente en un dominio de terceros.

DNS del origen                      ~60 ms
TCP con el origen                  ~150 ms   (1 RTT)
TLS con el origen                  ~150 ms   (1 RTT)
Peticion + proceso + primer byte   ~200 ms   (1 RTT + 50 ms de backend)
                                   -------
TTFB                               ~560 ms

Transferencia del HTML de 40 KB    ~300 ms   (excede la ventana inicial)
Parseo hasta el <link> del CSS      ~10 ms
Descarga del CSS de 30 KB          ~200 ms   (conexion ya abierta)
CSSOM + primer pintado              ~40 ms
                                   -------
FCP                              ~1.110 ms

DNS + TCP + TLS del dominio de la fuente  ~360 ms  (en paralelo, desde el parseo)
Descarga de la fuente de 20 KB            ~150 ms
Descarga de la imagen del hero de 120 KB  ~700 ms
Renderizado del elemento LCP               ~30 ms
                                          -------
LCP                                    ~2.000 ms

Fíjate en el reparto. De los dos segundos, 560 ms se han ido antes de recibir un byte de contenido y unos 360 ms se han ido en abrir una conexión a un tercero para traer 20 KB. Entre esos dos bloques está más del 45% del total, y ninguno se arregla escribiendo mejor JavaScript. La siguiente lección organiza los remedios disponibles y los empareja con estas causas.