🌐 Fundamentos Web ⚡ Performance, SEO & Accesibilidad

Cómo renderiza el navegador una página web: el pipeline crítico de renderizado

Del HTML en bruto a píxeles en pantalla: parsing, render tree, layout, paint y composición, y por qué cada fase tiene un impacto directo y medible en Core Web Vitals.

📅 9 de junio de 2026 ⏱️ 12 min de lectura ✍️ Equipo ProgramacionWebs

Cuando el servidor ya respondió con el HTML —el último paso de cómo funciona Internet, de la petición a la respuesta—, el trabajo no ha hecho más que empezar. Ese HTML es texto plano; para que se convierta en píxeles en una pantalla, el navegador tiene que ejecutar una secuencia de fases muy concreta, conocida como pipeline de renderizado o critical rendering path. No es una caja negra: entender sus fases es lo que te permite explicar por qué una página “tarda en pintar”, por qué un script mal colocado congela la pantalla en blanco, y por qué las métricas de Core Web Vitals miden exactamente lo que miden.

El pipeline de un vistazo

graph LR
A[HTML] --> B[Parsing → DOM]
C[CSS] --> D[Parsing → CSSOM]
B --> E[Style: DOM + CSSOM → Render Tree]
D --> E
E --> F[Layout: geometría y posición]
F --> G[Paint: rasterizar en píxeles]
G --> H[Composite: unir capas en pantalla]

Seis fases, no una. “El navegador está renderizando” es, en realidad, “el navegador está en alguna de estas seis fases, y cada una tiene un coste y unos disparadores distintos”. El motor separa además el trabajo en dos hilos: el hilo principal (main thread), que hace el parsing, ejecuta JavaScript y calcula estilo, layout y paint; y el hilo compositor, que puede ensamblar capas ya pintadas sin volver a pasar por el hilo principal. Esa separación es la que hace posible que ciertas animaciones vayan fluidas incluso con el hilo principal ocupado, como veremos más adelante.

Fase 1: parsing HTML y construcción del DOM

El motor de renderizado no espera a tener el HTML completo para empezar a trabajar: tokeniza el documento en el hilo principal a medida que llegan bytes de la red, identificando etiquetas de apertura, cierre, atributos y texto, y con esos tokens construye el árbol de nodos que ya conoces si has leído el DOM explicado a fondo. Cuantos más nodos tenga el documento, más tiempo cuesta esta fase — un HTML inflado con miles de nodos innecesarios no es solo un problema de peso de descarga, es trabajo extra de construcción de árbol antes de que exista nada que pintar.

En paralelo a este parsing, un mecanismo llamado preload scanner examina el marcado que todavía no se ha procesado por completo buscando referencias a hojas de estilo, scripts, fuentes e imágenes, y lanza sus descargas en segundo plano sin esperar a que el parser principal llegue hasta ahí. Es la razón por la que, en la práctica, muchos recursos empiezan a descargarse casi al mismo tiempo aunque estén declarados en puntos distintos del documento.

Fase 2: parsing CSS y el CSSOM

De forma simultánea, el navegador procesa cada hoja de estilos y construye el CSSOM (CSS Object Model): un árbol equivalente al DOM pero para reglas de estilo, que aplica la cascada CSS —de reglas generales del user-agent a las más específicas del sitio— y calcula el estilo computado final de cada posible selector.

Construir el CSSOM en sí es rápido, normalmente más rápido que una resolución DNS. El problema no es la velocidad de construcción: es que el navegador no puede empezar a calcular layout hasta tener el CSSOM completo, porque cualquier regla, en cualquier parte de cualquier hoja de estilos, podría afectar a la posición o el tamaño de cualquier elemento. No hay forma de saber si una regla al final del archivo va a cambiar algo del principio sin haber leído el archivo entero.

Esto convierte a toda hoja de estilos referenciada de forma síncrona en un recurso que bloquea el render por definición: el navegador construye el DOM sin esperar, pero no pinta nada hasta tener CSSOM y DOM juntos. Hay una salida parcial: los media types y media queries en el propio <link> permiten indicarle al navegador que un CSS concreto no es necesario para el render inicial y puede descargarse con prioridad menor, sin bloquear:

<!-- Bloquea el render en cualquier dispositivo -->
<link rel="stylesheet" href="estilos.css" />

<!-- No bloquea el render inicial: solo se aplica al imprimir -->
<link rel="stylesheet" href="print.css" media="print" />

<!-- No bloquea en pantallas pequeñas: el navegador la descarga igualmente,
     pero con menor prioridad, porque de entrada no aplica -->
<link rel="stylesheet" href="desktop.css" media="(min-width: 1024px)" />

Fase 3: por qué JavaScript bloquea el parsing, no solo el render

Un <script> sin async ni defer es más agresivo que un CSS bloqueante: detiene por completo el parsing del HTML en el punto exacto donde aparece, porque el script podría llamar a document.write() o modificar el DOM de formas que invalidarían todo lo que el parser hiciera a partir de ahí. El navegador tiene que descargar el script (si es externo), compilarlo y ejecutarlo antes de seguir tokenizando el resto del documento.

Hay un acoplamiento adicional que sorprende a quien no lo ha visto antes: si ese script aparece después de un <link rel="stylesheet">, el navegador también espera a que el CSSOM esté listo antes de ejecutarlo, porque el script podría consultar estilos computados (getComputedStyle) que dependen de él. Un script bloqueante colocado tras una hoja de estilos pesada hereda, de rebote, el bloqueo del CSS.

<link rel="stylesheet" href="estilos.css" />
<script src="analytics.js"></script>
<!-- Este script espera al CSSOM completo antes de poder ejecutarse,
     aunque su propio contenido no tenga nada que ver con estilos -->

Los atributos async y defer existen para romper ese bloqueo: async descarga el script en paralelo y lo ejecuta en cuanto está listo, sin garantía de orden respecto a otros scripts; defer lo descarga en paralelo pero retrasa la ejecución hasta que el parsing del HTML ha terminado, respetando el orden de aparición en el documento. Para scripts que no necesitan tocar el DOM antes de que exista, o que no dependen de otros scripts, casi siempre es preferible una de las dos opciones a dejar el bloqueo por defecto.

Fase 4: Style y Layout — construir la geometría real

Con DOM y CSSOM completos, el navegador los combina en un árbol de renderizado que asigna estilo computado a cada nodo visible. Los nodos con display: none quedan fuera por completo —ni ocupan espacio ni se pintan—, mientras que visibility: hidden sí se mantiene en este árbol: ocupa su espacio en el layout, solo que no se pinta al final.

El layout (también llamado reflow cuando ocurre después del primero) recorre ese árbol desde la raíz y calcula la posición y el tamaño exacto en píxeles de cada caja, partiendo del tamaño del viewport y aplicando el modelo de caja de cada elemento. Es una operación que se repite cada vez que algo que afecta a la geometría cambia: se inserta o elimina un nodo, cambia el tamaño de una imagen, se recalcula un valor % relativo a un contenedor que a su vez cambió. El DOM explicado a fondo ya cubre en detalle el anti-patrón más caro relacionado con esto —alternar lecturas y escrituras geométricas dentro de un bucle, forzando un reflow síncrono en cada iteración—, así que no lo repetimos aquí: es la consecuencia práctica más directa de entender esta fase.

Fase 5: Paint — de cajas a píxeles

El paint convierte cada caja ya posicionada en píxeles reales: rellena colores, dibuja texto, renderiza bordes, sombras e imágenes. No todo cuesta lo mismo: pintar un rectángulo de color sólido es barato; pintar una sombra difusa, un gradiente o texto con antialiasing es sensiblemente más caro de computar. En un dispositivo con pantalla de alta densidad, esto puede significar pintar varios millones de píxeles, y todo el trabajo del hilo principal —parsing, JavaScript, style, layout y paint— tiene que caber en poco más de 16 milisegundos si se quiere mantener 60 fotogramas por segundo.

Fase 6: Composite — el hilo que no depende del principal

La última fase ensambla las distintas capas ya pintadas en el orden correcto para formar el fotograma final. Aquí es donde entra el hilo compositor, que corre de forma separada del hilo principal. Ciertas propiedades CSS —transform, opacity, y con matices will-change— pueden resolverse íntegramente en esta fase sin volver a pasar por layout ni paint: el navegador ya tiene la capa pintada de antemano y solo necesita desplazarla, rotarla o cambiar su transparencia, algo que la GPU hace de forma prácticamente gratuita.

Es la razón técnica detrás de una recomendación de rendimiento muy repetida: animar con transform y opacity en lugar de top/left o width/height. Las primeras dos solo tocan composición; las segundas fuerzan layout y paint en cada fotograma de la animación, con el coste correspondiente.

Chrome documenta esta última parte del pipeline con más granularidad todavía bajo el nombre de RenderingNG: paint genera listas de dibujo, un paso de commit las traslada al hilo compositor, layerize las divide en capas compuestas, raster las convierte en texturas de GPU, y pasos finales de activación y agregación combinan todo en el fotograma que efectivamente se dibuja en pantalla. El detalle exacto de esa arquitectura interna cambia con cada versión del motor, pero el principio de fondo —separar trabajo costoso de layout/paint en el hilo principal del trabajo de composición en GPU— lleva siendo la estrategia central de Chromium desde hace años, y algo equivalente ocurre en Firefox y Safari con sus propios motores.

La relación directa con Core Web Vitals

Esto deja de ser teoría en el momento en que se traduce a las métricas que ya cubrimos en detalle en Core Web Vitals 2026:

  • LCP (Largest Contentful Paint) depende de que el elemento más grande visible llegue hasta el final del pipeline: se tiene que haber descargado su recurso (si es una imagen), pero también tienen que haberse completado el parsing del HTML hasta ese punto, el CSSOM necesario para su estilo, el layout que determina su tamaño y posición, y el paint final. Un CSS bloqueante innecesariamente grande, o un script síncrono colocado antes del contenido principal, retrasa cada una de esas fases y, por tanto, LCP directamente.
  • CLS (Cumulative Layout Shift) es, literalmente, el pipeline volviendo a ejecutar la fase de layout después de que el usuario ya veía algo en pantalla: una imagen sin width/height que “empuja” el contenido cuando termina de cargar, una fuente web que sustituye a la de sistema con métricas distintas, un banner inyectado por JavaScript sin reservar su espacio de antemano. Todo layout shift visible después del primer render es, en términos del pipeline, un reflow no anticipado.
  • INP (Interaction to Next Paint) depende de que el hilo principal esté libre para procesar la interacción, recalcular estilo y layout si hace falta, y llegar hasta un nuevo paint. Si el hilo principal está ocupado ejecutando una tarea larga de JavaScript cuando el usuario hace clic, la respuesta visual —el “next paint”— espera a que esa tarea termine, sin importar lo simple que sea la interacción en sí.

Ver estas tres métricas como “reglas de Google” en lugar de como consecuencias directas de fases concretas del pipeline es la forma más rápida de optimizar a ciegas. Cada una apunta a un cuello de botella distinto y verificable: descarga y bloqueo para LCP, estabilidad de layout para CLS, disponibilidad del hilo principal para INP.

Errores comunes al razonar sobre el pipeline

El primero es tratar “el navegador está renderizando” como una operación atómica, cuando en realidad son seis fases con costes y disparadores completamente distintos — optimizar paint no ayuda si el problema real es un CSSOM que tarda en construirse porque hay veinte hojas de estilo bloqueantes.

El segundo es asumir que async en un script resuelve cualquier problema de bloqueo. Resuelve el bloqueo del parsing, pero un script async que se ejecuta en mitad de la carga y modifica el DOM de forma agresiva puede seguir disparando reflows costosos exactamente igual que uno síncrono — el atributo cambia cuándo se ejecuta, no qué tan caro es lo que hace una vez ejecutado.

El tercero es sobreestimar la composición en GPU como solución universal: mueve trabajo del hilo principal a la GPU, pero no lo elimina, y un uso indiscriminado de capas compuestas puede convertirse en el nuevo cuello de botella, esta vez de memoria en lugar de CPU.

Qué controla realmente el developer en cada fase

  • Parsing HTML: menos nodos DOM innecesarios, HTML servido ya estructurado en lugar de construido enteramente por JavaScript en cliente cuando el contenido es crítico para el primer render.
  • CSSOM: CSS crítico inline, el resto diferido o cargado con media que no bloquee, evitar hojas de estilo gigantes que sirven a rutas que la página actual ni usa.
  • Bloqueo por JavaScript: defer por defecto para scripts que no necesitan ejecutarse antes de que exista contenido, async para scripts independientes sin orden que importe, nunca scripts síncronos bloqueantes antes del contenido visible.
  • Layout: dimensiones explícitas en imágenes y elementos que cargan de forma asíncrona, evitar leer y escribir propiedades geométricas alternadamente en bucles.
  • Paint y composición: animar con transform/opacity, reservar will-change para elementos que de verdad lo necesitan, vigilar sombras y efectos costosos en elementos que se repintan con frecuencia.

Ninguna de estas decisiones es una optimización aislada: todas actúan sobre una fase concreta de la misma secuencia, y esa secuencia es la misma que miden, en producción y con usuarios reales, las métricas que ya decidiste que te importan.

Compartir