wandres.dev
REUTILIZAR EN SVG · defs, use, symbol y sprites

Sprite externo frente a sprite en línea

use apuntando a otro fichero: qué gana en caché, qué restricciones de origen impone, qué se rompe con file y con un service worker, y el criterio de elección.

⏱ 16 min

use puede apuntar a un fragmento de otro fichero, y eso convierte el sprite en un recurso cacheable independiente del HTML. Suena a la mejor de las dos opciones y viene con una lista de condiciones: mismo origen obligatorio, una petición extra en la ruta crítica, un comportamiento distinto bajo el protocolo de fichero, y una interacción con el service worker que sorprende. El criterio de elección es concreto y depende de cuántas páginas y cuántos iconos tengas.

🎯 Al terminar esta lección sabrás
  • Escribir un use que referencia un fichero externo y saber qué exige.
  • Enumerar las cuatro restricciones del sprite externo.
  • Medir el punto en el que la caché compensa la petición extra.
  • Aplicar el criterio de elección a un sitio concreto.

La sintaxis y la primera condición

<svg class="ico" aria-hidden="true">
  <use href="/iconos/sprite.svg#i-buscar" />
</svg>

La ruta apunta al fichero y el fragmento al id del símbolo dentro. El fichero es un SVG normal con sus symbol, y necesita el atributo xmlns porque se sirve como documento independiente:

<svg xmlns="http://www.w3.org/2000/svg">
  <symbol id="i-buscar" viewBox="0 0 24 24">
    <circle cx="11" cy="11" r="7" fill="none" stroke="currentColor" stroke-width="2"/>
    <path d="M16 16l5 5" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
  </symbol>
</svg>

Sin ese xmlns el fichero no es un documento SVG válido y no se parsea. En el sprite en línea no hace falta porque el parser de HTML pone el elemento en el espacio de nombres SVG automáticamente; aquí no hay parser de HTML.

Las cuatro restricciones

Uno: mismo origen, sin excepciones. La referencia externa de use está sujeta a la política de mismo origen y no se puede habilitar con CORS. Un sprite alojado en un CDN de otro dominio no funciona, aunque el servidor mande todas las cabeceras del mundo. Si sirves los assets desde un subdominio distinto, no hay sprite externo.

Dos: no funciona bajo el protocolo de fichero. Abrir la página con doble clic desde el explorador, sin servidor, hace que cada fichero sea un origen opaco y las referencias fallan. Es una molestia real cuando alguien manda un HTML por correo para revisar algo, y la causa de más de un «a mí no me funciona».

Tres: es una petición más. El primer icono de la página no aparece hasta que el sprite se ha descargado y parseado. En una conexión lenta eso es un salto visual, y si el icono está en un botón sin texto, un botón vacío durante un segundo.

Cuatro: el fragmento es opaco para la caché de la primera visita. El navegador descarga el fichero completo aunque solo uses un símbolo. Con doscientos iconos y diez usados, descargas los doscientos.

Lo que sí funciona, y sorprende que funcione

La herencia cruza igual que en el sprite en línea. El contenido del fichero externo se instancia en un árbol de sombra que hereda del use, así que currentColor y las custom properties llegan:

<span style="color:#f38ba8; --ico-acento:#f9e2af">
  <svg class="ico"><use href="/iconos/sprite.svg#i-carpeta" /></svg>
</span>

Eso no era evidente: el contenido viene de otro documento, y podría haberse decidido que heredara de su propia raíz. Los motores actuales heredan del use, que es lo útil y lo que dice la especificación. Es el punto donde conviene comprobar en tus objetivos reales antes de comprometer una arquitectura, porque es la clase de detalle que ha cambiado con el tiempo.

Lo que no llega son los selectores. Igual que en el caso en línea, no hay forma de apuntar dentro.

La aritmética de la elección

El sprite externo gana cuando el ahorro por caché supera el coste de la petición extra. Eso depende de tres números: el peso del sprite, el número de páginas que visita un usuario típico, y si el HTML se cachea.

Un sprite de cincuenta iconos de trazo pesa alrededor de 8 kB en bruto y en torno a 2 kB tras Brotli. Si el usuario ve una sola página, el sprite externo cuesta una petición extra y ahorra cero. Si ve seis páginas y el HTML no se cachea, el sprite en línea ha viajado seis veces (12 kB comprimidos) frente a una (2 kB) más una petición.

Escenario Elección Motivo
Landing de una sola página En línea La petición extra no se amortiza nunca
Sitio de contenido, muchas páginas, HTML sin caché Externo El ahorro se multiplica por página
Aplicación de una sola página En línea, o mejor como componentes El HTML se carga una vez; la petición extra es ruido
Sitio con assets en otro dominio En línea, obligatorio El sprite externo no puede cruzar orígenes
Correo electrónico o PDF En línea, obligatorio No hay peticiones ni caché

Hay una cuarta opción que en un proyecto con empaquetador suele ganar a las dos: el icono como componente. Cada icono es un módulo que exporta su marcado, el empaquetador incluye solo los que se importan, y no hay ni sprite ni referencias por cadena. Se pierde la deduplicación cuando el mismo icono aparece cincuenta veces en una página (el marcado se repite), y se gana la eliminación de código muerto, la verificación en compilación y la posibilidad de pasar propiedades. Para una interfaz con muchos iconos distintos y pocas repeticiones, es la mejor opción de las cuatro.

El sprite externo y el service worker se llevan mal de una forma concreta

Un sprite externo referenciado desde use se pide con un tipo de destino de recurso distinto del de una imagen normal, y esa diferencia produce dos problemas reales con un service worker.

El primero: si tu estrategia de caché filtra por el destino de la petición y solo cachea image, script, style y document, el sprite se queda fuera y se pide por red en cada navegación. El síntoma es que los iconos tardan en aparecer en modo desconectado y todo lo demás funciona.

El segundo, y peor: el sprite se cachea con la versión del despliegue anterior. Los identificadores de los símbolos son una API pública, y si en el despliegue nuevo has renombrado o añadido iconos, el HTML nuevo referencia símbolos que el sprite cacheado no tiene. La página se ve con iconos ausentes hasta que el service worker se actualiza, lo que puede tardar una navegación o una sesión entera según tu ciclo de vida.

La defensa es la de siempre para cualquier recurso versionado: poner el hash del contenido en el nombre del fichero del sprite y referenciarlo desde el HTML con ese nombre. Así el sprite nuevo tiene una URL nueva, no hay caché que valga, y el sprite viejo puede quedarse en la caché sin hacer daño porque nadie lo pide. La regla general es que un recurso cuyo contrato puede cambiar entre despliegues no puede tener una URL estable, y un sprite es exactamente eso.

Un tercer detalle del mismo terreno: si sirves el sprite desde una ruta con un hash, el HTML tiene que llevar la ruta correcta, lo que significa que el HTML no puede estar cacheado agresivamente. Es la restricción normal del versionado por contenido, pero aquí sorprende porque nadie piensa en un sprite de iconos como en un recurso versionado.

Verificar que funciona antes de comprometerse

Tres comprobaciones rápidas antes de elegir sprite externo en un proyecto:

  1. Abre la página desde el servidor de desarrollo y confirma que los iconos aparecen. Después ábrela con el protocolo de fichero y confirma que no aparecen: si aparecen, es que el sprite no es externo de verdad.
  2. Cambia el color del contenedor y confirma que currentColor cruza.
  3. Mira la pestaña de red y confirma que el sprite se pide una sola vez por navegación, no una por icono.

Si las tres pasan, el sprite externo está bien montado y la única decisión que queda es la de caché y versionado.

⚔️ Reto práctico

Monta el mismo conjunto de iconos de las tres maneras (sprite en línea, sprite externo y componentes) en un sitio de cuatro páginas. Mide el peso total transferido en una visita de cuatro navegaciones, con y sin caché, y el momento en que el primer icono se pinta. Con esos números, decide cuál usarías; y anota a partir de cuántos iconos y cuántas páginas cambiaría tu respuesta.