preload: declarar lo que el HTML esconde
Qué hace exactamente una precarga, por qué el atributo as no es opcional, las variantes para imágenes adaptativas y fuentes, y la forma por cabecera HTTP con Early Hints.
El escáner de precarga descubre pronto todo lo que esté escrito como atributo en el HTML, y no descubre nada de lo que esté detrás de una hoja de estilos o de un módulo de JavaScript. preload es el mecanismo declarativo para contarle al navegador que un recurso escondido detrás de esa indirección es crítico, y que debe pedirlo ya. Es la pista más potente del catálogo y la única que puede empeorar tu página por sí sola.
- Explicar qué hace una precarga y en qué se diferencia de una petición normal.
- Justificar por qué el atributo
ascambia el resultado y no solo la etiqueta. - Escribir precargas correctas para fuente, imagen adaptativa y módulo de datos.
- Emitir precargas desde la cabecera HTTP y saber cuándo eso gana tiempo real.
Qué hace y qué no hace
Una precarga es una petición declarativa desacoplada del uso. El navegador descarga el recurso con la prioridad y el destino que corresponden a su tipo, y lo guarda en una caché de memoria a la espera de que alguien lo pida de verdad. No lo aplica, no lo ejecuta, no lo inserta en ningún sitio.
Esa separación es toda la idea. Una hoja de estilos referenciada con rel="stylesheet" se descarga y bloquea el renderizado. La misma hoja con rel="preload" as="style" se descarga y no bloquea nada: queda esperando. Un script referenciado con <script src> se descarga y se ejecuta en el momento que dicten defer o async; con rel="preload" as="script" se descarga y no se ejecuta hasta que aparezca la etiqueta real.
De ahí salen sus dos usos legítimos, y solo esos dos son legítimos:
Uno: adelantar el descubrimiento de un recurso que el escáner no ve. La imagen de fondo declarada en CSS, la fuente declarada en una regla @font-face, el módulo que se importa desde dentro de otro módulo. Lo desarrollamos en el escáner de precarga: todo lo que exija descargar y parsear otro recurso primero se descubre tarde por construcción.
Dos: desacoplar la descarga de la aplicación. Bajar la hoja de estilos de la vista secundaria sin que bloquee la primera, o bajar el JavaScript de un diálogo antes de que el usuario lo abra.
Lo que no es un uso legítimo: poner una precarga sobre un recurso que ya está referenciado como atributo en el HTML unas líneas más abajo. El escáner ya lo había visto. No adelantas nada y sí subes su prioridad por encima de recursos que quizá la necesitaban más.
El atributo as no es decorativo
Es el error más frecuente y el más caro, porque su síntoma es una descarga duplicada que no salta a la vista.
as declara el destino de la petición, y el destino determina cuatro cosas a la vez:
- La prioridad. Una precarga con
as="style"sale con prioridad máxima; conas="image"sale con prioridad baja. Es el mismo esquema de prioridades implícitas que vimos en la prioridad implícita. - Las cabeceras de la petición.
AcceptySec-Fetch-Destse rellenan según el destino. Unas="image"envía elAcceptde imágenes, con los formatos que el navegador acepta; unas="fetch"no. - La política de seguridad de contenido que se aplica. Una precarga de script se valida contra la directiva de scripts, no contra la genérica.
- La identidad de la entrada en caché. Y esta es la que produce la descarga doble.
El navegador solo reutiliza la precarga si la petición real coincide en URL, en destino, en modo de credenciales y en política de referente. Si has escrito as="fetch" para algo que después pides como as="script", no coincide: descargas el recurso dos veces.
Sin as, el navegador no puede saber nada de eso. Descarga con prioridad baja, con el destino genérico, y la petición real casi nunca coincide. El resultado es la peor combinación posible: has pagado los bytes dos veces y encima has llegado tarde.
<!-- MAL: sin as. Doble descarga garantizada. -->
<link rel="preload" href="/css/critico.css">
<!-- BIEN. -->
<link rel="preload" as="style" href="/css/critico.css">
Los valores de as que vas a usar en la práctica son style, script, font, image, fetch, video, audio y track. Hay más en la especificación, y son casos de nicho.
Las fuentes web se piden en modo anónimo por especificación, incluso las de tu propio dominio. Una precarga sin crossorigin abre una petición en modo con credenciales que no coincide con la petición real, y el navegador descarga la fuente dos veces: una para nadie y otra para el CSS. La regla es memorizable porque no tiene excepciones: toda precarga de fuente lleva crossorigin, sea del origen que sea.
<link rel="preload" as="font" type="font/woff2" href="/fuentes/inter-var.woff2" crossorigin>Las variantes que hacen falta de verdad
El atributo type como filtro de soporte. Si lo declaras, el navegador solo descarga el recurso si sabe decodificar ese tipo. Es lo que hace segura una precarga de AVIF: los navegadores que no lo entienden se saltan la etiqueta en lugar de descargar bytes inservibles.
<link rel="preload" as="image" href="/img/hero.avif" type="image/avif" fetchpriority="high">
El atributo media para precargas condicionales. Acepta una consulta de medios completa y se evalúa antes de descargar. Sirve para no precargar en móvil la imagen de escritorio:
<link rel="preload" as="image" href="/img/hero-360.avif" type="image/avif"
media="(max-width: 600px)">
<link rel="preload" as="image" href="/img/hero-1600.avif" type="image/avif"
media="(min-width: 601px)">
Los atributos imagesrcset e imagesizes para imágenes adaptativas. Cuando la imagen real usa srcset, la precarga tiene que ejecutar el mismo algoritmo de selección o descargará un archivo distinto del que acabará usándose. Para eso existen estos dos atributos, que son el reflejo exacto de los del elemento de imagen:
<link rel="preload" as="image"
imagesrcset="/img/hero-640.avif 640w, /img/hero-1280.avif 1280w, /img/hero-1920.avif 1920w"
imagesizes="(max-width: 700px) 100vw, 700px"
type="image/avif"
fetchpriority="high">
Cuando usas imagesrcset, el atributo href actúa de reserva para navegadores que no entiendan srcset, y puedes omitirlo si no te importan. Si pones href a secas y la imagen real elige otra fuente del srcset, tendrás dos descargas de la misma imagen en dos tamaños, que es exactamente la regresión que la precarga pretendía evitar.
Los datos con as="fetch". Para adelantar una respuesta de API que el JavaScript va a pedir después. Requiere crossorigin si la petición real se hace con mode: 'cors', que es el valor por defecto de la API de obtención de recursos para orígenes ajenos:
<link rel="preload" as="fetch" href="/api/panel-inicial.json" crossorigin>
// La peticion real reutiliza la precarga si el modo coincide.
const datos = await fetch('/api/panel-inicial.json').then((r) => r.json());
La precarga por cabecera HTTP
La etiqueta <link> tiene una limitación estructural: el navegador no la ve hasta que le llegan los primeros bytes del HTML. Si tu servidor tarda 400 milisegundos en generar la página, esos 400 milisegundos son tiempo muerto en el que la red está ociosa.
La cabecera Link mueve la pista por delante del cuerpo de la respuesta. Sintaxis idéntica a la etiqueta, con la URL entre ángulos y los parámetros separados por punto y coma:
Link: </css/critico.css>; rel=preload; as=style
Link: </fuentes/inter-var.woff2>; rel=preload; as=font; type=font/woff2; crossorigin
Link: </img/hero.avif>; rel=preload; as=image; type=image/avif; fetchpriority=high
Se pueden agrupar en una sola cabecera separando por comas, y así lo hacen la mayoría de los servidores:
Link: </css/critico.css>; rel=preload; as=style, </img/hero.avif>; rel=preload; as=image
Configuración real en nginx, donde always garantiza que la cabecera se envía también en respuestas de error y de no modificado:
location = /index.html {
add_header Link "</css/critico.css>; rel=preload; as=style" always;
add_header Link "</fuentes/inter-var.woff2>; rel=preload; as=font; type=font/woff2; crossorigin" always;
}
Esto todavía no gana los 400 milisegundos: la cabecera va en la respuesta final, que llega igual de tarde. Lo que sí los gana es enviar esas mismas cabeceras en una respuesta informativa antes de la definitiva. Es el mecanismo de las pistas tempranas, con código de estado 103:
HTTP/1.1 103 Early Hints
Link: </css/critico.css>; rel=preload; as=style
Link: <https://imagenes.ejemplo.com>; rel=preconnect
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Link: </css/critico.css>; rel=preload; as=style
...
El servidor emite la respuesta 103 en cuanto sabe qué recursos hará falta, sigue generando la página, y envía el 200 después. El navegador empieza a descargar el CSS mientras el backend todavía está trabajando. En un servidor con medio segundo de tiempo de generación, eso es medio segundo.
El soporte es desigual y hay que tratarlo como mejora progresiva: Chrome lo implementa desde la versión 103, varias CDN lo emiten automáticamente, y fuera de Chromium la cobertura es parcial. Un navegador que no entienda el 103 lo descarta y espera al 200 sin romperse. Manda solo pistas sobre recursos que estés seguro de necesitar: aquí un error no se corrige, ya has gastado el ancho de banda.
Cuando alguien descubre preload, el primer impulso es aplicarlo a lo que ya sabe que es crítico: el CSS principal, el script del framework, la imagen del hero que ya está en el HTML. Y todos esos casos son exactamente aquellos en los que no hace nada, porque el escáner de precarga los había descubierto en el mismo milisegundo. Peor: el CSS ya tiene prioridad máxima, así que la etiqueta es inerte; pero la imagen sí cambia de prioridad, y una precarga de imagen con prioridad alta puesta antes de la hoja de estilos puede colarse por delante del CSS bloqueante en una conexión estrecha. Acabas de retrasar el primer pintado para adelantar una imagen que se veía igual de rápido. La regla que separa el uso útil del ritual es de una frase: precarga solo lo que el escáner no puede ver. Si puedes encontrar la URL del recurso con un grep sobre el HTML que devuelve tu servidor, no la precargues, ya la ha visto el navegador. Y si de verdad quieres cambiar el orden entre dos recursos que el escáner ya conoce, la herramienta correcta no es preload, es fetchpriority, que ajusta la prioridad sin duplicar la declaración ni arriesgarse a una segunda descarga por desajuste de destino.
Coge la página más lenta de tu sitio y haz este inventario en tres pasos. Primero, lista todas las etiquetas de precarga del HTML. Segundo, para cada una comprueba con curl -s URL | grep si el recurso aparece también como atributo en el propio HTML: si aparece, la precarga es redundante y candidata a borrarse. Tercero, para las que queden, comprueba en el panel de red que cada recurso se descarga una sola vez. Cualquier archivo que aparezca dos veces tiene un desajuste de as, de crossorigin o de URL, y ahora mismo te está costando el doble de bytes.