gzip y brotli: la compresión que va después del minify
Minificar reduce la redundancia semántica en el build; comprimir con gzip o brotli reduce la redundancia estadística al transferir. Son capas complementarias porque atacan dos redundancias distintas: el minificador reescribe lo que el compresor no puede tocar, y el compresor empaqueta lo que al minificador no le compensa quitar. Por qué el orden importa y por qué se mide el tamaño comprimido.
Minificar no es el último paso antes de que tu código llegue al usuario: falta una capa más, la compresión en el transporte. Cuando el navegador pide tu app.js, el servidor puede enviarlo comprimido con gzip o brotli, y el navegador lo descomprime de forma transparente antes de ejecutarlo. Es tentador ver minify y compresión como dos formas de lo mismo —hacer el archivo pequeño— pero son operaciones distintas que atacan redundancias distintas, y precisamente por eso se potencian. Entender por qué son complementarias, y no redundantes, es entender por qué el número que de verdad importa es el tamaño ya comprimido, no el que produce el minificador.
- Separar la minificación en el build de la compresión en el transporte HTTP.
- Entender por qué son complementarias: redundancia semántica frente a redundancia estadística.
- Comparar
gzip,brotliy el emergentezstd, y distinguir compresión estática de dinámica. - Comprender por qué el orden es minify-luego-comprimir y por qué se mide el tamaño comprimido.
Dos capas, dos momentos, dos redundancias
La distinción clave es cuándo ocurre cada cosa. La minificación ocurre en el build, una sola vez, y produce un código fuente más corto. La compresión ocurre en la transferencia, en cada respuesta HTTP, y produce menos bytes por el cable; el cliente los descomprime de forma transparente antes de que el motor los vea. Son capas encadenadas: primero minificas el archivo, luego el servidor lo comprime al enviarlo.
Conviene tener presentes las dos coordenadas de cada capa:
- Cuándo: el minify en el build, una sola vez; la compresión en cada respuesta HTTP.
- Quién: el minify lo hace tu bundler; la compresión, tu servidor o tu CDN.
- Sobre qué: el minify reescribe el código fuente; la compresión codifica sus bytes.
- Quién revierte: nadie revierte el minify —es código válido—; el navegador descomprime la compresión al vuelo.
Que se potencien en vez de estorbarse se explica porque cada una elimina una redundancia que la otra no puede tocar. El minificador ataca la redundancia semántica: renombra nombreDeUsuario a o, borra una rama muerta, colapsa sintaxis. Un compresor jamás podría hacer eso, porque no entiende el programa —no sabe que renombrar esa variable o borrar esa rama es seguro—; solo ve bytes. El compresor, a su vez, ataca la redundancia estadística: las subcadenas que se repiten —function, return, .length, los identificadores que sobreviven— y la codifica con menos bits. El minificador no se molesta en eso porque su salida sigue siendo texto plano lleno de repeticiones.
Cada capa tiene su presa, y no se pisan:
- Minify, redundancia semántica: renombra, poda y colapsa; exige entender el programa.
- Compresión, redundancia estadística: codifica subcadenas repetidas con menos bits; ignora el significado.
- Lo que el minify deja: las mil repeticiones de
return,functiono.lengthque aún quedan. - Lo que la compresión no puede: renombrar una variable o borrar una rama, porque no sabe si es seguro.
El efecto acumulado se ve mejor con las capas apiladas sobre un mismo archivo:
# El mismo archivo, capa a capa (cifras ilustrativas)
app.js 1000 KB fuente sin tocar
app.min.js 320 KB tras minificar
app.min.js.br 90 KB tras brotli, lo que de verdad descarga el usuario
Aquí está la raíz de la complementariedad. Un compresor de propósito general trata tu JavaScript como una secuencia de bytes cualquiera: encuentra patrones repetidos y los codifica más corto, sin la menor idea de qué significan. No puede renombrar una variable porque no sabe si es seguro; no puede borrar una rama porque no sabe si es inalcanzable. El minificador sí lo sabe, porque razona sobre el AST y sobre los ámbitos. Por eso ninguna de las dos capas hace obsoleta a la otra: el minificador hace las transformaciones que exigen entender el código, y el compresor exprime la redundancia que queda cuando ya no se puede entender nada más, solo contar repeticiones.
gzip, brotli y el emergente zstd
gzip es el veterano universal: implementa DEFLATE —una combinación de LZ77 y codificación Huffman— es rápido, está en todas partes y ofrece una ratio decente. brotli, creado por Google, comprime notablemente mejor el texto —a menudo entre un quince y un veinticinco por ciento más pequeño que gzip— gracias a un diccionario incorporado de cadenas comunes de la web y a niveles de calidad del 0 al 11. Su nivel máximo es lento, pensado para precomprimir una vez; los niveles intermedios sirven para comprimir al vuelo.
En 2026 asoma una tercera capa: zstd, adoptado ya como codificación de contenido HTTP —Content-Encoding: zstd— por varios CDN. Ofrece una relación velocidad/ratio excelente y compite de tú a tú con brotli siendo más rápido de comprimir. La negociación es siempre la misma danza de cabeceras: el cliente anuncia lo que acepta y el servidor responde con lo que eligió.
# El navegador anuncia lo que sabe descomprimir
Accept-Encoding: gzip, br, zstd
# El servidor responde con la codificacion elegida
Content-Encoding: br
Vary: Accept-Encoding
La cabecera Vary: Accept-Encoding es imprescindible: le dice a las cachés intermedias que la respuesta depende de la codificación aceptada, para que no le sirvan brotli a un cliente que solo entiende gzip.
La negociación sigue siempre los mismos pasos:
- El cliente envía
Accept-Encodingcon la lista de lo que sabe descomprimir. - El servidor elige la mejor que ambos soporten y responde con
Content-Encoding. - Añade
Vary: Accept-Encodingpara que las cachés no mezclen versiones incompatibles. - El navegador descomprime de forma transparente antes de entregar el recurso al motor.
gzip
DEFLATE, universal y rápido. La red de seguridad que todo cliente entiende. Ratio decente, sin sorpresas.
brotli
Mejor ratio en texto por su diccionario web. Nivel 11 lento para precomprimir; niveles medios para el vuelo.
zstd
El emergente de 2026. Ratio comparable a brotli y más veloz al comprimir. Adoptado ya por varios CDN como Content-Encoding.
brotli y zstd exponen niveles de calidad, y elegir bien depende de cuándo comprimes. Si precomprimes en el build —una sola vez por artefacto— usa el nivel máximo: el tiempo extra no le cuesta nada a tus usuarios y exprime hasta el último byte. Si comprimes al vuelo, en cada respuesta, baja a un nivel intermedio, porque el máximo quemaría CPU del servidor por una mejora marginal. El error clásico es comprimir dinámicamente al nivel más alto y pagar latencia en cada petición por unos bytes que la precompresión te habría dado gratis.
Estática o dinámica, y por qué el orden importa
Hay dos maneras de comprimir. La dinámica comprime cada respuesta al vuelo, necesaria para contenido que cambia, y usa niveles moderados para no gastar demasiada CPU por petición. La estática precomprime los archivos en el build —genera un app.js.br y un app.js.gz al máximo nivel una sola vez— y el servidor entrega ese archivo ya hecho, con cero coste de CPU por petición y la mejor ratio posible. Para los assets estáticos de tu bundle, precomprimir es casi siempre lo correcto.
Las dos estrategias reparten el coste de forma opuesta:
- Estática: comprime en el build al máximo nivel, una vez; el servidor sirve el
.brya hecho, con cero CPU por petición. - Dinámica: comprime cada respuesta al vuelo, necesaria para contenido cambiante, a un nivel moderado para no quemar CPU.
- En la práctica: precomprime los assets del bundle y deja la dinámica para el HTML que se genera por petición.
- En un CDN: a menudo la compresión es automática, y basta con no estorbarla sirviendo ya comprimido lo que no toca.
# Precomprimir en el build: se calcula una vez, se sirve mil veces
app.js # minificado, lo que despliegas
app.js.br # brotli nivel 11, para clientes que aceptan br
app.js.gz # gzip de respaldo para el resto
El orden de las dos capas no es negociable: primero minify, luego comprimir. Minificar antes le da al compresor un texto más corto y más regular sobre el que trabajar, y el resultado combinado es más pequeño que cualquiera de las dos capas por separado. Comprimir un código sin minificar desperdicia trabajo: el compresor suda más para un resultado peor, porque carga con nombres largos y ramas muertas que el minificador habría borrado de raíz.
La compresión de la que hablamos viaja en Content-Encoding: describe cómo está codificado el recurso de punta a punta, y las cachés la respetan junto con Vary. No la confundas con Transfer-Encoding, que describe cómo se trocea el mensaje en un salto concreto de la conexión y no sobrevive a los intermediarios. Para servir tus assets comprimidos, Content-Encoding con gzip, br o zstd es la vía correcta.
flowchart LR A[Codigo fuente] --> B[Minificar en el build] B --> C[Menos redundancia semantica] C --> D[Comprimir al transferir] D --> E[Menos redundancia estadistica] E --> F[El navegador descomprime solo] style B fill:#89b4fa,color:#11111b style D fill:#a6e3a1,color:#11111b style F fill:#f9e2af,color:#11111b
Una advertencia final: no comprimas lo ya comprimido. Las imágenes png o jpg, el vídeo y las fuentes woff2 ya están comprimidos —woff2 usa brotli por dentro— así que volver a pasarles gzip o brotli gasta CPU para no ganar nada, y a veces incluso engorda el archivo. La compresión en el transporte es para el texto: JavaScript, CSS, HTML, SVG, JSON.
En la práctica, la línea entre comprimir y no comprimir es nítida:
- Sí: JavaScript, CSS, HTML, SVG, JSON y, en general, cualquier texto.
- No: imágenes
png,jpgowebp, vídeo, y fuenteswoff2, ya comprimidas por dentro. - Cuidado: volver a comprimir lo ya comprimido gasta CPU y a veces engorda el archivo.
- Mide siempre: el número que importa es el tamaño con
Content-Encoding, no el del archivo en disco.
Cierras el nivel con la imagen completa del camino de tus bytes al usuario, y con la lección que lo unifica todo. Minificar y comprimir no son dos intentos rivales de hacer lo mismo, sino dos capas que atacan dos redundancias de naturaleza distinta y que, por eso, se suman en vez de solaparse. El minificador opera con entendimiento: razona sobre el AST, resuelve ámbitos, prueba qué es seguro, y hace las transformaciones que solo puede hacer quien comprende el programa —renombrar, podar, colapsar—. El compresor opera con estadística: ignora por completo el significado y exprime las repeticiones que quedan cuando ya no hay más que entender. Ninguno puede sustituir al otro, porque ninguno puede hacer el trabajo del otro: pídele a brotli que borre una rama muerta y no sabrá si es seguro; pídele a terser que codifique con menos bits las mil apariciones de return y no es su oficio. Juntos, en ese orden —minify primero, compresión después— convierten tu carpeta src en la mínima cantidad de bytes que puede viajar hasta un navegador sin perder ni un ápice de comportamiento. Y de aquí sale la consecuencia estratégica que reordena todas las decisiones del nivel: el único número que de verdad cuenta es el tamaño ya comprimido, porque es lo único que tu usuario descarga. Ese hecho relativiza casi todo lo demás. Relativiza la carrera por el minificador que produce el crudo más pequeño, porque brotli aplana buena parte de esa ventaja. Reordena tus prioridades hacia lo que la compresión no puede arreglar —menos código, mejor troceado, cargar menos al arranque— porque ahí es donde queda margen real. Y te da una disciplina de medición infalible: no celebres un .js minificado pequeño, mide el .br que sirve tu servidor, porque esa es la cifra que paga la persona al otro lado. Quien interioriza que hay dos capas complementarias y un solo número que importa deja de optimizar por intuición y empieza a optimizar por lo que de verdad viaja por el cable.
- Toma tu
app.jsminificado y comprímelo congzip -9y conbrotli -q 11; anota los tres tamaños y calcula cuánto añade cada capa sobre la anterior. - Comprime también la versión sin minificar y confirma que minify-luego-comprimir gana a solo comprimir.
- Inspecciona en las DevTools la cabecera
Content-Encodingde tus respuestas y verifica que sirvesbrozstda un cliente que los acepta. - Configura la precompresión estática en tu build o tu servidor y comprueba que se sirve el
.brya hecho en vez de comprimir al vuelo. - Intenta comprimir un
woff2o unpngy verifica que no ganas bytes: razona por qué la compresión de transporte es solo para texto.