Prioridades: por qué el navegador pide las cosas en ese orden
Cómo asigna Chrome la prioridad de cada recurso, qué hacen fetchpriority y preload en el waterfall, y por qué a veces un preload empeora las cosas.
El navegador no pide los recursos en el orden en que aparecen en el documento: los ordena según una escala de prioridad que asigna con heurísticas propias basadas en el tipo de recurso, su posición en el marcado, sus atributos y, en el caso de las imágenes, si están en la parte visible al cargar. Entender esa escala explica por qué tu recurso importante sale el último, y las herramientas para modificarla explican por qué a veces intentar arreglarlo lo empeora.
- Leer la columna de prioridad y entender cómo se asigna cada valor.
- Explicar el efecto de
preload,preconnectyfetchprioritysobre el orden. - Diagnosticar un recurso crítico que se pide demasiado tarde.
- Reconocer los tres casos en que un
preloadempeora el resultado.
La escala y cómo se asigna
Chrome usa cinco niveles de prioridad, de más a menos: muy alta, alta, media, baja y muy baja. La columna correspondiente del panel de red los muestra.
Las asignaciones habituales, que conviene conocer porque explican casi todo el orden que se observa.
El documento tiene la máxima prioridad, evidentemente.
Las hojas de estilo de la cabecera tienen prioridad muy alta, porque bloquean el renderizado.
Los scripts varían mucho según sus atributos y su posición. Un script sin async ni defer en la cabecera es alto, porque bloquea el parser. Uno con defer baja, porque se ejecuta al final. Uno con async queda en un punto intermedio y variable.
Las fuentes referenciadas desde CSS tienen prioridad alta, pero con un matiz decisivo: no se descubren hasta que el CSS que las referencia se ha descargado y se ha determinado que hacen falta. Ese retraso de descubrimiento es la causa número uno de texto que aparece con la fuente de reserva y salta después.
Las imágenes empiezan en prioridad baja y el navegador las sube a alta si detecta que están en la parte visible de la página durante el layout inicial. Esa promoción ocurre después del primer cálculo de layout, así que la imagen principal de una página se pide más tarde de lo que su importancia sugeriría.
Las peticiones de datos iniciadas por script tienen prioridad alta por defecto, lo cual sorprende: una llamada de analítica sale con más prioridad que la imagen principal de la página, salvo que se le diga lo contrario.
Los recursos de precarga heredan la prioridad del tipo que declaren.
La prioridad afecta a dos cosas distintas: el orden en que el navegador emite las peticiones, y —con la segunda generación del protocolo— el peso que se le pide al servidor que dé a cada flujo dentro de la conexión multiplexada. La segunda parte depende de que el servidor implemente la priorización, cosa que no todos hacen bien.
Las herramientas para cambiar el orden
preload. Le dice al navegador que descubra y descargue un recurso inmediatamente, antes de encontrarlo en el flujo normal. Su valor está en los recursos que se descubren tarde: una fuente referenciada desde CSS, una imagen definida en una hoja de estilos, un módulo importado dinámicamente.
<link rel="preload" href="/fuentes/principal.woff2" as="font" type="font/woff2" crossorigin>
El atributo as es obligatorio en la práctica: sin él, el navegador no sabe qué prioridad darle ni cómo tratarlo, y puede acabar descargando el recurso dos veces. Y en las fuentes, crossorigin es obligatorio aunque estén en tu propio dominio, porque las fuentes siempre se piden en modo anónimo y sin ese atributo la precarga no se reutiliza.
preconnect. No descarga nada: abre la conexión con un origen por adelantado, adelantando la resolución de nombres y el apretón de manos. Es la herramienta correcta cuando sabes que vas a pedir algo de un dominio pero no sabes qué.
fetchpriority. Un atributo que ajusta la prioridad de un recurso concreto, con valores high, low y auto. Es la herramienta más directa y la que menos efectos secundarios tiene.
<img src="/hero.avif" fetchpriority="high" alt="">
<img src="/decorativa.avif" fetchpriority="low" alt="">
Subir la imagen principal a prioridad alta evita esperar a la promoción por layout, y bajar las decorativas libera ancho de banda para lo que importa.
También funciona en peticiones desde script.
fetch('/api/critico', { priority: 'high' });
fetch('/api/analitica', { priority: 'low' });
Diagnosticar un recurso que llega tarde
El procedimiento, en tres pasos.
Uno: identifica cuándo se pidió. En el waterfall, la posición horizontal del inicio de la barra. Si la barra empieza tarde, el problema es de descubrimiento; si empieza pronto y es larga, es de transferencia.
Dos: mira quién lo inició. La columna de iniciador dice si lo descubrió el parser del documento —lo antes posible— o si lo descubrió un script o una hoja de estilos, lo cual implica esperar a que eso llegue.
Tres: mira su prioridad. Si es baja y debería ser alta, fetchpriority lo arregla. Si ya es alta pero se pidió tarde, el problema es de descubrimiento y la herramienta es preload.
Esa distinción entre descubrimiento tardío y prioridad baja es la que decide qué herramienta usar, y confundirlas produce cambios que no sirven de nada.
Los tres casos en que preload empeora las cosas
Uno: precargar demasiado. El ancho de banda es finito. Precargar quince recursos significa que los quince compiten desde el principio y ninguno llega antes; lo que consigues es retrasar los que de verdad importaban. La regla práctica es que un preload solo tiene sentido si puedes nombrar el recurso concreto que estabas esperando.
Dos: precargar algo que no se usa. Un preload con as mal puesto, o de un recurso que la página acaba no necesitando, es ancho de banda gastado en nada. Chrome avisa en la consola cuando un recurso precargado no se usa en los primeros segundos, y ese aviso merece leerse.
Tres: precargar sin crossorigin en una fuente. El recurso se descarga dos veces: una por la precarga y otra por el uso real, porque no coinciden los modos de petición. El síntoma es dos entradas idénticas en el panel de red, y es sorprendentemente frecuente.
Hay un cuarto caso relacionado que conviene mencionar: precargar en lugar de arreglar la causa. Si una fuente se descubre tarde porque está referenciada en una hoja de estilos que a su vez se importa desde otra hoja, la solución no es precargar la fuente sino deshacer la cadena de importaciones, que además está retrasando todo lo demás.
Hay un mecanismo del navegador que explica la mayor parte de la diferencia de velocidad entre una página tradicional y una aplicación renderizada en cliente, y que conviene conocer porque cambia cómo se valora una decisión de arquitectura. Mientras el parser principal construye el DOM y se detiene en cada script bloqueante, hay un segundo parser especulativo que corre por delante leyendo el marcado en bruto y buscando URLs. Ese escáner encuentra las imágenes, las hojas de estilo y los scripts que vienen más adelante en el documento y empieza a descargarlos antes de que el parser principal llegue a ellos, con lo que el tiempo que el parser pasa bloqueado se aprovecha para la red en lugar de perderse. Es una optimización enorme y completamente invisible, y es la razón por la que un HTML servido entero se carga tan bien. La consecuencia incómoda es directa: el escáner solo ve lo que está en el marcado que llega del servidor. Una imagen cuya URL se construye en JavaScript, una hoja de estilos importada desde otra, una fuente referenciada dentro de un CSS, un componente cuyo contenido se inserta después de hidratar: nada de eso existe para el escáner, así que su descubrimiento se retrasa hasta que el código correspondiente se descarga, se parsea y se ejecuta. Esa es exactamente la escalera de la lección anterior, y es la razón por la que las aplicaciones de una sola página tienen cascadas tan escalonadas. Las tres decisiones que se derivan de entender esto: pon en el HTML inicial todo lo que puedas, aunque sea una versión simplificada; usa preload precisamente para los recursos que el escáner no puede ver, que es su caso de uso legítimo y no un parche general; y cuando evalúes una estrategia de renderizado, cuenta cuántos recursos críticos quedan fuera del alcance del escáner, porque ese número predice la longitud de la cadena crítica mejor que ninguna métrica de tamaño.