Por qué el CSS bloquea el renderizado
La razón técnica de que el navegador espere al CSS antes de pintar, cómo hacer que una hoja de estilos no bloquee, y qué es el CSS crítico con sus problemas reales.
Que el CSS bloquee el renderizado no es una limitación técnica ni un descuido histórico: es una decisión deliberada que evita un problema peor. Entender qué problema evita es lo que permite decidir con criterio cuándo desactivarlo, porque desactivarlo se puede, y a veces conviene.
- Explicar por qué el navegador espera al CSSOM completo antes de pintar.
- Hacer que una hoja de estilos no bloquee el renderizado, con tres técnicas.
- Evaluar el CSS crítico con honestidad, incluidos sus costes de mantenimiento.
- Reconocer el efecto secundario del CSS sobre la ejecución de scripts.
El problema que evita el bloqueo
Imagina que el navegador pintara sin esperar al CSS. Recibe el HTML, construye el DOM y lo pinta con los estilos por defecto: texto negro sobre fondo blanco, todo apilado, enlaces azules subrayados. Un segundo después llega la hoja de estilos y la página cambia radicalmente de aspecto.
Ese fenómeno tiene nombre, destello de contenido sin estilo, y era habitual en los navegadores de los años noventa. Es una experiencia peor que esperar: el usuario ve algo ilegible, empieza a leer, y el contenido se reorganiza bajo su mirada. En términos de las métricas modernas, es un desastre de estabilidad visual.
La razón técnica de fondo es la asimetría entre DOM y CSSOM que ya vimos. El HTML es útil parcialmente y el CSS no. Una regla que llegue en el último byte de una hoja puede afectar a cualquier elemento de la página. El navegador no puede saber si la parte que falta va a cambiar lo que está a punto de pintar, así que la decisión segura es esperar.
De ahí sale la definición operativa: una hoja de estilos referenciada desde el HTML bloquea el primer pintado si su condición de medio se cumple. Esa última condición es la que abre la puerta a todas las técnicas.
Cómo hacer que no bloquee
Tres técnicas, de menos a más recomendable.
Uno: la condición de medio. Una hoja de estilos cuya condición de medio no se cumple no bloquea el renderizado. Se descarga igualmente, pero con prioridad más baja y sin detener el pintado.
<!-- Bloquea, porque el medio coincide. -->
<link rel="stylesheet" href="/css/principal.css">
<!-- No bloquea: solo aplica al imprimir. -->
<link rel="stylesheet" href="/css/impresion.css" media="print">
<!-- No bloquea en pantallas estrechas. -->
<link rel="stylesheet" href="/css/escritorio.css" media="(min-width: 1024px)">
La segunda línea es un caso real y frecuente: los estilos de impresión no tienen ningún motivo para bloquear la pantalla y muchos sitios los sirven sin condición de medio.
Dos: el truco de la carga diferida por medio. Se aprovecha lo anterior declarando un medio que no se cumple y cambiándolo cuando la hoja ha cargado:
<link
rel="stylesheet"
href="/css/secundario.css"
media="print"
onload="this.media='all'">
<noscript><link rel="stylesheet" href="/css/secundario.css"></noscript>
Funciona en todos los navegadores y es un poco truco. El elemento <noscript> garantiza que los estilos se apliquen si no hay JavaScript.
Tres: el atributo explícito de bloqueo. El enfoque moderno y declarativo consiste en decir explícitamente qué bloquea el renderizado con el atributo blocking. Combinado con una precarga que se convierte en hoja de estilos al cargar, permite expresar la intención sin trucos:
<!-- Carga sin bloquear y se aplica cuando este lista. -->
<link
rel="preload"
as="style"
href="/css/secundario.css"
onload="this.rel='stylesheet'">
El motivo por el que el CSS bloquea es evitar el destello de contenido sin estilo. Cuando lo desactivas, estás aceptando ese riesgo a cambio de pintar antes. Si la hoja diferida afecta a la geometría de contenido visible, su llegada producirá un desplazamiento que sumará al CLS. La técnica solo es segura para estilos que afectan a contenido por debajo del pliegue, o que no cambian el layout.
El CSS crítico, con honestidad
La técnica del CSS crítico consiste en extraer las reglas necesarias para pintar la parte visible inicial, incrustarlas directamente en el HTML, y cargar el resto de forma no bloqueante.
La lógica es impecable: si solo necesitas 6 KB de reglas para pintar lo que se ve, y esos 6 KB viajan dentro del propio documento, eliminas un recurso crítico entero de la ruta. El primer pintado ocurre en cuanto llega el HTML.
Y es una de las técnicas con peor relación entre beneficio y coste de mantenimiento del catálogo. Los problemas reales:
Determinar qué es crítico es difícil y frágil. Depende del tamaño de la ventana, del contenido concreto de la página, del estado del usuario. Las herramientas que lo automatizan cargan la página en un tamaño concreto y extraen lo que aplica; una ventana distinta puede necesitar reglas distintas.
Se desincroniza. El CSS crítico extraído hoy deja de ser correcto cuando alguien cambia el diseño. Si la extracción no está automatizada en cada construcción, degenera en pocas semanas.
Engorda el documento. Los bytes incrustados viajan en cada carga y no se cachean por separado. Si tu CSS crítico son 20 KB, has añadido 20 KB al recurso más importante de la ruta, y probablemente lo has sacado de la ventana inicial de la conexión. El coste puede superar al beneficio.
Duplica. Las reglas incrustadas también están en la hoja completa que se carga después, así que se transfieren dos veces.
La recomendación de la propia documentación de optimización de LCP es matizada y merece citarse: incrustar la hoja de estilos solo se recomienda si es pequeña, porque el contenido incrustado no se beneficia de la caché en cargas posteriores; y si una hoja es tan grande que tarda más en cargar que el recurso del LCP, es poco probable que sea buena candidata.
El orden de preferencia sensato es este: primero reduce el tamaño de tu CSS eliminando lo que no se usa y dividiéndolo por ruta; si tras eso la hoja crítica sigue siendo grande, plantéate el CSS crítico con extracción automatizada; y solo incrusta lo que de verdad quepa sin sacar el documento de la ventana inicial.
El efecto sobre los scripts
Un efecto secundario que sorprende y que explica retrasos aparentemente inexplicables.
Un script síncrono no puede ejecutarse hasta que el CSSOM esté completo. La razón es que el script podría consultar estilos calculados, y el navegador tiene que garantizar que la respuesta sea correcta.
Eso significa que este código tiene una dependencia que no se ve:
<head>
<link rel="stylesheet" href="/css/enorme.css">
<script src="/js/inicio.js"></script>
</head>
El script no se ejecutará hasta que la hoja de estilos haya llegado completa, aunque el script ya esté descargado y no tenga nada que ver con el CSS. Y como el script bloquea el parser, el resto del documento tampoco avanza.
La cadena resultante es: descargar CSS, construir CSSOM, ejecutar script, seguir parseando. Cuatro pasos en serie donde parecía haber dos cosas independientes.
La solución es la misma de siempre y la desarrollamos en la lección de los atributos de carga: ese script no debería ser síncrono. Con defer, se descarga en paralelo, no bloquea el parser y se ejecuta al final.
El CSS crítico se adopta por fe, porque suena obviamente correcto, y se abandona en silencio dos años después cuando nadie se acuerda de cómo se regenera. Antes de implementarlo hay una comprobación de diez minutos que decide el asunto con datos. Mide dos cosas: cuánto pesa tu documento HTML comprimido, y cuánto tarda en llegar tu hoja de estilos crítica. Si el documento está holgadamente por debajo de la ventana inicial de la conexión, unos catorce kilobytes, y tu CSS crítico extraído cabe dentro de ese margen, adelante: vas a eliminar un viaje de ida y vuelta completo por un coste de bytes que no cambia el número de rondas. Si al incrustar el documento se pasa de esa ventana, has cambiado un viaje por otro y no has ganado nada, además de haber añadido una pieza frágil a tu proceso de construcción. Y si tu hoja de estilos crítica llega en menos de lo que tarda el recurso de tu elemento principal, el CSS no era tu cuello de botella y estabas a punto de optimizar la parte equivocada. Esas tres cifras se obtienen en un minuto cada una y evitan la mayor parte de las implementaciones de CSS crítico que no sirven para nada.