Lo que cuesta una data URI
Una data URI mete un archivo dentro de una URL: data:image/png;base64,iVBORw0KGgo.... Puedes usarla en cualquier sitio donde vaya una URL, así que un icono puede vivir dentro del atributo src del HTML o de un background-image de CSS, y el navegador nunca hace una petición aparte para él.
Las contrapartidas son:
- Tamaño. Base64 deja la carga en 4/3 del original, alrededor de un 33 % más (fórmula exacta en El aumento de tamaño de Base64). El prefijo
data:image/png;base64,añade otros 22 bytes. - Caché. El archivo incrustado solo se guarda en caché como parte del documento que lo contiene. Un logotipo incrustado en cada página HTML se vuelve a descargar con cada página. Incrustado en un archivo CSS, se guarda en caché con ese CSS, y cambiar un solo icono invalida toda la hoja de estilos.
- Renderizado. El navegador no puede pintar nada hasta que llega el CSS que bloquea el renderizado. Una hoja de estilos que lleva 200 KB de fuentes o imágenes incrustadas retrasa el primer renderizado lo que tarden en descargarse esos bytes. En HTML, los datos incrustados empujan el contenido que viene detrás más abajo en el flujo de bytes.
- Sin carga diferida.
loading="lazy"en un<img>con data URI no ahorra nada, porque los bytes ya llegaron con la página.
A cambio, te ahorras una petición. Con HTTP/1.1 eso era importante, porque los navegadores abrían solo unas pocas conexiones por host. Con HTTP/2 y HTTP/3, muchas peticiones comparten una conexión, así que el ahorro es menor: sobre todo las cabeceras de la petición y de la respuesta, más un viaje de ida y vuelta de latencia para los archivos que el navegador descubre tarde.
Medido: lo que hacen gzip y Brotli con Base64
La cifra del 33 % es para texto sin comprimir. La mayor parte del HTML y del CSS se sirve con gzip o Brotli, así que la cifra que importa es el tamaño comprimido. Lo medimos nosotros mismos.
Método. Archivos de muestra de paquetes de Debian 13: un icono PNG de 16×16 (tema de iconos HighContrast), un PNG RGBA de 48×48 (tema hicolor), el fondo de pantalla JPEG azul de Xfce de 2880×1800, un JPEG de 800×500 redimensionado a partir de él con Pillow a calidad 80, la fuente WOFF2 Roboto Slab Regular y dos iconos SVG (un icono simbólico de Adwaita de 479 bytes y la espiral de Debian de GNOME de 7,6 KB). Cada archivo y su codificación Base64 (solo la carga, sin el prefijo data:) se comprimió con zlib de Node.js 24: gzip a nivel 9 y Brotli a calidad 11, los ajustes habituales para recursos estáticos precomprimidos. Los tamaños están en bytes.
| Archivo | Original | Base64 | Original gzip | Base64 gzip | Original Brotli | Base64 Brotli |
|---|---|---|---|---|---|---|
| PNG 16×16 | 281 | 376 | 285 | 306 | 285 | 284 |
| PNG 48×48 | 4,552 | 6,072 | 4,575 | 4,602 | 4,551 | 4,589 |
| JPEG 800×500 | 8,195 | 10,928 | 7,425 | 7,173 | 7,160 | 6,908 |
| JPEG 2880×1800 | 197,011 | 262,684 | 182,684 | 183,945 | 180,114 | 181,140 |
| Fuente WOFF2 | 52,532 | 70,044 | 52,538 | 52,765 | 52,536 | 52,690 |
| Icono SVG | 479 | 640 | 291 | 427 | 247 | 410 |
| Espiral SVG | 7,610 | 10,148 | 3,123 | 4,398 | 2,582 | 3,823 |
Lo que muestran las cifras:
Los binarios ya comprimidos pierden casi todo el aumento. Los datos PNG, JPEG y WOFF2 ya están comprimidos y son casi aleatorios, así que volver a comprimirlos gana poco (los dos PNG incluso crecieron un poco con gzip). El Base64 de bytes aleatorios usa 64 símbolos de 8 bits cada uno, y un codificador Huffman lo reduce a unos 6 bits por símbolo. El Base64 comprimido quedó entre el 96 % y el 107 % del binario comprimido, y entre el 84 % y el 109 % del archivo original. Los JPEG de esta prueba eran a su vez algo comprimibles, por eso sus versiones Base64 llegaron a ocupar un poco menos que el JPEG con gzip; la diferencia es demasiado pequeña para planificar en torno a ella. En el diminuto PNG de 16×16, los 18 bytes fijos de cabecera y cola de gzip pesan mucho en porcentaje, y el formato más ligero de Brotli los hizo desaparecer.
SVG sale peor parado. SVG es texto con mucha repetición, y la compresión se aprovecha de ello: la espiral pasó de 7.610 a 3.123 bytes con gzip. Base64 reparte cada grupo de 3 bytes en 4 caracteres cuyos valores dependen de la alineación, así que una subcadena repetida ya no se ve igual dos veces. El SVG en Base64 con gzip ocupó entre un 41 % y un 47 % más que el SVG con gzip, y con Brotli entre un 48 % y un 66 % más.
SVG con codificación URL frente a Base64
SVG no necesita Base64 en absoluto, porque ya es texto. Una data URI con codificación URL lo mantiene legible:
.flag {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M3 0v16h2V8h3l1 2h5V2h-4L9 0z'/%3E%3C/svg%3E");
}
Lo mínimo que hay que escapar es %, # (iniciaría un fragmento de URL), < y >, más el tipo de comilla que delimite el atributo. Cambiar las comillas dobles dentro del SVG por simples evita tener que escaparlas. Los caracteres que no son ASCII deben codificarse por porcentajes como UTF-8.
Los mismos dos SVG, medidos de la misma forma:
| SVG | Codificación | Tamaño | Gzip | Brotli |
|---|---|---|---|---|
| Icono | Base64 | 640 | 427 | 410 |
| Icono | Codificación URL mínima | 493 | 293 | 253 |
| Icono | encodeURIComponent | 723 | 326 | 279 |
| Espiral | Base64 | 10,148 | 4,398 | 3,823 |
| Espiral | Codificación URL mínima | 7,179 | 3,057 | 2,549 |
| Espiral | encodeURIComponent | 11,594 | 3,319 | 2,750 |
«Mínima» significa espacios en blanco colapsados, comillas dobles cambiadas por simples y solo %, #, <, > escapados. Se comprimió más o menos al mismo tamaño que el SVG original; la espiral incluso queda algo más pequeña porque colapsar los espacios eliminó bytes. El encodeURIComponent completo ocupa más sin comprimir, pero aun así se comprime mucho mejor que Base64. La herramienta SVG a data URI genera ambas formas una al lado de otra.
Base64 sigue teniendo sentido para SVG cuando una herramienta o API solo acepta Base64, o cuando el SVG contiene caracteres que prefieres no tener que escapar.
Content Security Policy y data:
Con una Content Security Policy, 'self' no incluye las URLs data:. Cada directiva que deba permitirlas tiene que mencionar el esquema:
Content-Security-Policy: default-src 'self'; img-src 'self' data:; font-src 'self' data:
Permitir data: en img-src y font-src es habitual y de poco riesgo. Permitirlo en script-src, default-src u object-src es otra cosa: deja que un atacante capaz de inyectar marcado aporte un script sin necesitar ningún host externo, lo que anula buena parte del sentido de CSP. Si las imágenes incrustadas chocan con tu política, amplía solo img-src.
Los navegadores también impiden que las páginas naveguen la ventana principal a una URL data:, ya sea por enlace, redirección o script, así que una data URI se puede incrustar en una página, pero no abrir como enlace de nivel superior.
Cuándo merece la pena incrustar
Suele merecer la pena:
- Iconos diminutos e imágenes de interfaz por debajo de 1–2 KB, sobre todo los necesarios para el primer pintado. El coste de la petición es una parte grande de su tamaño, y la compresión borra casi todo el coste de Base64.
- HTML de un solo archivo. Informes sin conexión, documentación exportada, páginas de cobertura de tests y vistas previas de correos HTML que deben funcionar al abrirse desde el disco.
- CSS crítico que de todos modos va incrustado en el HTML, donde una imagen de fondo pequeña viaja con él.
- Contenido generado, como un gráfico renderizado a PNG por un script e incrustado en un informe HTML.
No suele merecer la pena:
- Fotos y cualquier cosa de más de unos pocos kilobytes. Es mejor servirlas como archivos aparte con cachés de larga duración.
- Imágenes compartidas entre muchas páginas, que se volverían a descargar dentro de cada respuesta HTML.
- Fuentes web. Un archivo WOFF2 suele ocupar decenas de kilobytes (el medido arriba tiene 52 KB), incrustarlo en el CSS hace que la hoja de estilos bloquee el renderizado durante más tiempo, y los archivos de fuente separados se pueden guardar en caché y precargar.
- Todo lo que queda por debajo de la parte visible. Un archivo aparte con
loading="lazy"no cuesta nada hasta que se necesita.
El correo electrónico es un caso aparte. El soporte de imágenes data: varía entre clientes de correo, así que prueba en los clientes que usan realmente tus destinatarios. La forma fiable de incrustar una imagen en un correo HTML es un adjunto MIME en línea referenciado con cid:, que sigue siendo Base64, pero dentro de la estructura MIME. Consulta Base64 en el correo electrónico.
Suelta un PNG, JPEG, WebP o SVG y obtén la data URI y su tamaño. El archivo no sale de tu navegador.
Preguntas frecuentes
¿Las imágenes Base64 son más lentas que las imágenes normales?
No por sí mismas. Las imágenes incrustadas pequeñas evitan una petición, mientras que las grandes inflan el HTML o el CSS, no se pueden guardar en caché por separado y pueden retrasar el renderizado. Incrustar ayuda por debajo de unos pocos kilobytes y perjudica por encima.
¿gzip elimina el 33 % extra de Base64?
Con PNG, JPEG y WOFF2, casi por completo: en nuestras mediciones, el Base64 comprimido con gzip quedó a pocos puntos porcentuales del archivo binario. Con SVG y otros textos no, y el SVG en Base64 con gzip ocupó entre un 41 % y un 47 % más que el SVG con gzip.
¿Qué es mejor para SVG en línea: codificación URL o Base64?
La codificación URL. Es legible, editable y se comprime más o menos igual que el SVG original. Escapa como mínimo #, %, < y >, y usa comillas simples dentro del SVG si el atributo usa comillas dobles.
¿Por qué la Content Security Policy bloquea mis imágenes con data URI?
CSP trata data: como un esquema aparte que 'self' no cubre. Añade data: a img-src (o a font-src para fuentes). No lo añadas a script-src ni a default-src.