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.
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.
Verificar que funciona antes de comprometerse
Tres comprobaciones rápidas antes de elegir sprite externo en un proyecto:
- 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.
- Cambia el color del contenedor y confirma que
currentColorcruza. - 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.