Las imágenes en Base64 te cuestan la caché

Una URI de datos mete un archivo directamente en el documento: la imagen viaja dentro del HTML o del CSS en lugar de como una petición aparte. Durante años fue una optimización habitual, y el razonamiento que la sostenía ha caducado en buena parte.

Lo que cuesta

Base64 codifica tres bytes como cuatro caracteres, así que la forma codificada es un 33 % más grande que el archivo. Es algo propio de la codificación y no un detalle de implementación: usa 64 caracteres imprimibles para representar bytes cualesquiera, y 64 es 2⁶, así que en cada carácter que podría llevar ocho bits van seis. El prefijo data:image/png;base64, añade un par de docenas de caracteres más, que no es nada en una fotografía y sí algo en un icono de 200 bytes.

Conviene ser preciso con ese 33 %, porque se exagera a menudo. Es completamente real en el archivo en disco, en el documento analizado y en memoria. En la red, en su mayor parte no. La compresión de texto se lo come: toma 12 KB de datos de imagen ya comprimidos, que es lo que es un JPEG o un WebP, y la forma en Base64 son 16.000 caracteres, pero con gzip vuelve a 12.070 bytes frente a los 12.023 del original comprimido. Es una penalización de transferencia de menos de medio punto, no de un tercio. La compresión no encuentra patrones dentro de los datos de imagen, pero los encuentra con facilidad en el alfabeto de 64 caracteres que se les pone encima.

Así que el argumento del tamaño es más débil de lo que suele parecer. El de la caché no.

Una imagen en un archivo aparte se descarga una vez, se guarda en caché con su propia URL y se reutiliza en cada página que la usa y en cada visita de vuelta. Una incrustada es parte del documento. Se vuelve a descargar con cada página que la lleva, se vuelve a decodificar cada vez y nunca puede guardarse en caché por separado del texto que la rodea, así que cambiar una palabra de la página invalida también la imagen.

Para un logotipo en una sola página, eso no es nada. Para un logotipo en todas las páginas de un sitio es estrictamente peor que un archivo aparte, y la diferencia crece con el número de páginas, no con el tamaño de la imagen.

Lo que pierdes además de la caché

El coste menos evidente es que una imagen incrustada deja de ser una imagen para el resto de la plataforma.

Pierdes Porque
loading="lazy" ya llegó con el documento
srcset y <picture> cada variante sería otra copia completa incrustada
Redimensionado y elección de formato en la CDN ya no queda ninguna URL que interceptar
Una vida en caché propia caduca cuando caduca el documento
Un error útil una URI mal formada simplemente no se muestra

Hay también un matiz con la política de seguridad de contenidos. Una política estricta de img-src 'self' bloquea directamente las URI de datos, y permitirlas supone añadir data: a la directiva. Es una ampliación pequeña pero real: data: en img-src se considera en general aceptable, mientras que data: en script-src desde luego que no, y es fácil relajar las dos a la vez por costumbre.

El sitio donde va importa más de lo que se cree. El CSS bloquea el renderizado: una hoja de estilos se descarga y se analiza entera antes de que el navegador pinte nada. Medio mega de Base64 dentro de una hoja de estilos retrasa el primer pintado de toda la página, incluidas todas las partes que no tienen nada que ver con la imagen.

Por qué se encogió la ventaja

Con HTTP/1.1 los navegadores abrían unas seis conexiones por servidor y las peticiones hacían cola unas detrás de otras. Quitar una petición ayudaba de verdad, y técnicas como los sprites y la incrustación existían por eso.

HTTP/2 y HTTP/3 multiplexan muchas peticiones sobre una conexión, así que el coste de un segundo archivo ahora es pequeño y la cuenta que justificaba incrustar ya casi no se sostiene. La misma época trajo el Server Push de HTTP/2, pensado para eliminar el viaje de ida y vuelta que quedaba, y Chrome le retiró el soporte en 2022 porque costaba más hacerlo bien de lo que valía. El patrón es coherente: la petición dejó de ser la parte cara.

Cuándo sigue siendo la decisión correcta

Situación Por qué
Icono pequeño en un archivo CSS evita una petición por unos cientos de bytes
Correo en HTML las imágenes externas se bloquean por defecto
Un único archivo autocontenido no hay recursos que mandar al lado
Marcador de posición crítico se muestra antes de que cargue nada más

La práctica se ha asentado en una franja difusa más que en una línea: unos pocos kilobytes es tan poco que ahorrar la petición compensa, diez o más es tanto que no, y lo del medio depende de cuántas veces se sirve la página y de si la misma imagen aparece en ella más de una vez. La repetición decide más a menudo que el tamaño. Una ilustración de 20 KB en una página de aterrizaje que nadie vuelve a visitar está bien; un icono de 2 KB incrustado en todas las páginas de un sitio de documentación no, y ningún umbral en kilobytes te lo va a decir.

La excepción del SVG

Con SVG, olvídate de Base64. Un SVG es texto, así que codificarlo con porcentajes en el CSS da un resultado más pequeño que el Base64 del mismo archivo: la codificación con porcentajes solo amplía los caracteres que de verdad hay que escapar, mientras que Base64 lo amplía todo un tercio. Un icono de 299 bytes de este sitio son 400 caracteres en Base64 y 329 con el escapado mínimo que exige el CSS.

Además se puede leer después en la hoja de estilos, lo que a veces importa, y cambiar un color de relleno es una edición en lugar de una recodificación.

Preguntas que hace la gente

¿Codificar vuelve a comprimir la imagen? No debería. Codificar los bytes originales da una imagen idéntica byte a byte; pasar por un lienzo la volvería a codificar.

¿Puedo usar URI de datos en una etiqueta img? Sí, y en url() del CSS, y en un elemento image de SVG.

¿Funcionan en el correo? Casi nunca: Gmail y Outlook las quitan o las rechazan. Es el único caso en el que la respuesta es un adjunto.

¿Hay un límite de tamaño? Los navegadores aceptan varios megas, pero una URI de datos así hace que el archivo que la contiene sea imposible de revisar a mano y lento de procesar.

¿Y si lo que hay que entregar es un solo archivo? Entonces incrústalo todo, y el argumento desaparece. Un informe sin conexión, una plantilla, una página enviada como adjunto: no hay nada que guardar en caché por separado, así que el único coste que queda es el tercio que la compresión casi elimina.

La herramienta de imagen a Base64 codifica los bytes originales y te da la forma para CSS o HTML, codificar en Base64 y decodificar Base64 se ocupan del texto, las tres de momento en inglés, y comprimir imágenes es el paso que merece la pena antes que nada, porque un tercio de un archivo más pequeño es un tercio más pequeño.