Precargar la fuente crítica: cuál, cuántas y en qué orden
Por qué el atributo crossorigin no es opcional aunque la fuente sea de tu propio origen, cuántas fuentes se pueden precargar sin perjudicar al resto, y dónde va la etiqueta dentro de la cabecera.
Precargar una fuente es la única forma de romper la dependencia entre la hoja de estilos y la petición del fichero, y ahorra de forma fiable entre 150 y 350 milisegundos en una conexión móvil. También es la directiva que más se escribe mal de todo el repertorio de pistas al navegador: sin un atributo concreto se descarga dos veces, y precargando de más se retrasa exactamente lo que se quería acelerar.
- Escribir una precarga de fuente correcta y explicar cada uno de sus cuatro atributos.
- Justificar por qué el modo de origen cruzado es obligatorio incluso en el mismo origen.
- Decidir cuántas fuentes precargar y con qué criterio.
- Verificar en el panel de red que la precarga ha hecho lo que esperabas.
La etiqueta correcta, atributo por atributo
<link rel="preload" as="font" type="font/woff2" href="/fuentes/cuerpo.woff2" crossorigin>
rel="preload" le dice al escáner de precarga que empiece la descarga ya, mientras el parseo del HTML continúa. Es lo que rompe la cadena: la petición arranca antes de que exista el CSSOM.
as="font" determina la prioridad con la que se pide y, sobre todo, hace que el recurso descargado se guarde en el sitio correcto para que la petición posterior del CSS lo encuentre. Sin este atributo la precarga se descarga con prioridad baja y no se reutiliza.
type="font/woff2" permite al navegador descartar la precarga si no soporta el formato. Con WOFF2 esto es teórico en 2026, y sigue siendo buena costumbre porque no cuesta nada.
crossorigin es el que hay que entender bien. Sin valor equivale a anonymous, que es lo que se quiere.
Por qué crossorigin es obligatorio siempre
La especificación de CSS obliga a que las fuentes se soliciten en modo de origen cruzado anónimo, sin credenciales, incluso cuando la URL es del mismo origen que el documento. La razón es de privacidad y de coherencia: una fuente no debería comportarse distinto según de dónde venga, y el modo con credenciales permitiría distinguirlo.
La consecuencia es que la petición que el navegador va a hacer para la fuente lleva el modo anónimo. Y la caché de precarga empareja por URL más modo de petición. Si tu precarga no lleva crossorigin, la hiciste en modo con credenciales, no empareja con la petición real, y el navegador descarga el fichero dos veces: una por la precarga, que se descarta sin usar, y otra por el CSS.
El síntoma es un aviso en la consola diciendo que el recurso se precargó y no se usó en los primeros segundos, y una entrada duplicada en el panel de red. Es el error de precarga más frecuente de todos y cuesta exactamente el doble de bytes.
La comprobación es de una línea:
const n = performance.getEntriesByType('resource').filter((r) => r.name.includes('cuerpo.woff2')).length;
console.log(n === 1 ? 'una sola descarga' : `DUPLICADA: ${n} peticiones`);
Cuántas y cuáles
Precargar tiene un coste que no aparece en ningún sitio: la fuente se pide antes que cosas que probablemente importan más. En Chromium, una fuente precargada se pide con prioridad alta, por debajo de la hoja de estilos bloqueante pero por encima de las imágenes normales. Cuatro fuentes precargadas de 25 KB son 100 KB de tráfico prioritario compitiendo con tu imagen del hero por el mismo ancho de banda, en el peor momento de la carga.
El criterio que uso, y que no he visto fallar:
Precarga una fuente. Como mucho dos. La que se usa en el texto visible sin desplazar la página. Todo lo demás llega cuando llegue.
No precargues pesos que no aparecen en la primera pantalla. La negrita del pie de página, la cursiva de las citas, la fuente de los titulares de segundo nivel: nada de eso está en el camino crítico. Con una fuente variable esto se simplifica solo, porque hay un único fichero para todos los pesos.
No precargues subconjuntos que quizá no se usen. Si tienes la fuente partida por rangos de caracteres, precargar el rango cirílico en un sitio en castellano es tirar 20 KB. Precarga el rango latino básico y deja que el emparejamiento decida el resto.
No precargues si la fuente es optional y la reserva está bien ajustada y no te importa. Es la excepción: precargar tiene sentido con optional precisamente porque sube la tasa de acierto, y si has decidido que la reserva ajustada es suficientemente buena, puedes ahorrarte la precarga entera.
Y la comprobación de que sobra o falta es empírica: quita la precarga, mide el instante de inicio de la petición de la fuente y el elemento mayor con contenido; vuelve a ponerla, mide otra vez. Si el elemento mayor con contenido empeora al precargar, estás robando ancho de banda a algo más importante.
Dónde va dentro de la cabecera
El escáner de precarga procesa la cabecera en orden, y entre recursos de la misma prioridad ese orden se traduce en el orden de las peticiones. Entre prioridades distintas manda la prioridad, no el orden.
La disposición que funciona:
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- 1. La hoja bloqueante: prioridad maxima, va primero pase lo que pase -->
<link rel="stylesheet" href="/estilos/principal.css">
<!-- 2. La imagen del hero, si es el elemento mayor con contenido -->
<link rel="preload" as="image" href="/img/hero.avif" fetchpriority="high">
<!-- 3. La fuente del cuerpo -->
<link rel="preload" as="font" type="font/woff2" href="/fuentes/cuerpo.woff2" crossorigin>
<title>...</title>
</head>
El orden entre los puntos 2 y 3 es una decisión real y depende de qué sea tu elemento mayor con contenido. Si es una imagen, la imagen va primero: es lo que la métrica está midiendo. Si es un bloque de texto —cosa frecuente en sitios editoriales y en páginas de producto— la fuente es lo que bloquea esa métrica y va primero.
Un detalle que ahorra sorpresas: la cabecera HTTP Link hace lo mismo que la etiqueta y se procesa antes, porque llega con las cabeceras de la respuesta, antes del primer byte del cuerpo. Si tu servidor o tu red de distribución lo permite, mover ahí la precarga de la fuente crítica adelanta la petición unas decenas de milisegundos más:
Link: </fuentes/cuerpo.woff2>; rel=preload; as=font; type=font/woff2; crossorigin
Hay una advertencia asociada: algunas configuraciones convierten esa cabecera en un empujón del servidor, que es una tecnología retirada de los navegadores. Comprueba que tu proveedor la trata como precarga y no como otra cosa.
El aviso de la consola diciendo que un recurso se precargó y no se usó en los primeros segundos es el aviso que más se ignora de todas las herramientas de desarrollo, y detrás hay siempre uno de cuatro problemas concretos. Vale la pena tenerlos en una lista, porque el mensaje es el mismo para los cuatro y el arreglo es distinto.
Uno: falta crossorigin. Ya explicado. Es la causa en la mayoría de los casos y produce una descarga duplicada completa. Si el fichero pesa 30 KB, has añadido 30 KB de tráfico prioritario a cambio de nada.
Dos: la URL no coincide exactamente. La precarga empareja por URL literal. Una barra de más, un parámetro de consulta que el CSS añade y la precarga no, una versión con hash que se genera en compilación y una precarga escrita a mano con el nombre sin hash: cualquiera de esas cosas produce dos ficheros distintos a ojos del navegador. Este es el que más tiempo hace perder porque la URL parece la misma. La forma de que no ocurra es generar la etiqueta de precarga desde el mismo sitio que genera la URL del CSS, nunca a mano.
Tres: la fuente no se usa en esa página. Precargas la fuente de titulares en una plantilla que no tiene titulares con esa familia. Ocurre siempre que la precarga vive en una cabecera compartida entre plantillas distintas. La solución es que la precarga sea parte de la plantilla, no del diseño base.
Cuatro, y es el más sutil: el unicode-range no empareja. Tienes la fuente partida en rango latino y rango latino extendido, precargas el extendido, y la página no contiene ni un solo carácter de ese rango. El navegador no pide esa cara nunca, y tu precarga cuelga sin destinatario. Es exactamente el mecanismo del emparejamiento funcionando bien, y tu precarga saltándoselo.
El cuarto caso tiene una variante que produce el efecto contrario y que es un buen argumento para no fragmentar demasiado: si la página tiene una sola comilla tipográfica o una raya de diálogo, y esos caracteres están en un rango de puntuación separado, se descarga ese fichero entero por un carácter. He visto páginas descargando cuatro ficheros de fuente donde tres de ellos aportaban menos de diez glifos visibles.
La auditoría que encuentra los cuatro casos de una vez, y que merece estar en la lista de comprobación de cada despliegue:
const precargadas = [...document.querySelectorAll('link[rel="preload"][as="font"]')]
.map((l) => l.href);
const descargadas = performance.getEntriesByType('resource')
.filter((r) => /\.woff2?(\?|$)/.test(r.name));
console.table(precargadas.map((url) => {
const coincidencias = descargadas.filter((r) => r.name === url);
return {
fichero: url.split('/').pop(),
peticiones: coincidencias.length,
diagnostico:
coincidencias.length === 0 ? 'PRECARGADA Y NO USADA'
: coincidencias.length > 1 ? 'DUPLICADA: revisa crossorigin'
: 'correcta',
};
}));
const noPrecargadas = descargadas.filter((r) => !precargadas.includes(r.name));
if (noPrecargadas.length) {
console.log('descargadas sin precargar:', noPrecargadas.map((r) => ({
fichero: r.name.split('/').pop(),
inicioMs: Math.round(r.startTime),
kb: Math.round(r.encodedBodySize / 1024),
})));
}La segunda parte es tan útil como la primera: las fuentes que se descargan sin estar precargadas, con su instante de inicio y su peso. Si una de ellas arranca a los 800 milisegundos y pesa 30 KB, tienes ahí un candidato claro. Y si son cinco, la conversación no es sobre precarga: es sobre por qué tu página necesita cinco ficheros de fuente.
Verificar que ha servido de algo
Tres columnas del panel de red responden la pregunta entera.
El instante de inicio de la petición de la fuente. Antes de precargar suele estar entre 400 y 800 milisegundos, después de la hoja de estilos. Después de precargar debería estar prácticamente pegado al de la propia hoja, alrededor de 100 o 200. Esa diferencia es el ahorro.
La prioridad. Una fuente precargada correctamente aparece con prioridad alta. Si aparece con prioridad baja, el as está mal o falta.
El número de peticiones del mismo fichero. Una. Si son dos, falta crossorigin.
Y la verificación de que ha servido para la métrica y no solo para la cascada: mide el instante en que se resuelve la promesa de fuentes listas, antes y después. Es el número que se traduce en experiencia.
const t = performance.now();
document.fonts.ready.then(() => console.log('fuentes listas a', Math.round(performance.now() - t + t), 'ms'));
Ejecuta la auditoría de precargas en tus tres plantillas principales. Corrige lo que salga marcado y anota el instante de inicio de la fuente del cuerpo antes y después. Después haz el experimento contrario: quita la precarga y mide el elemento mayor con contenido. Si mejora sin la precarga, tenías una fuente compitiendo con algo más importante, y esa es una conclusión que solo se obtiene midiendo las dos direcciones.