Gzip vs Brotli vs Zstd na Compressão de Assets Web
Brotli, gzip ou zstd para assets web: quando usar cada um, como o nível de compressão afeta o tamanho e como pré-comprimir arquivos estáticos.
Use Brotli para assets de texto estáticos, gzip como fallback universal e zstd para respostas dinâmicas nas quais você controla tanto o servidor quanto a CDN.
Muitos bundles ainda são entregues com gzip porque alguma ferramenta de build ou um padrão de hospedagem fez essa escolha anos atrás, e ninguém mais revisou o algoritmo ou o nível desde então. O restante deste artigo aborda por que conteúdo estático e dinâmico são problemas diferentes, por que o nível de compressão frequentemente altera mais o resultado do que o nome do algoritmo, como pré-comprimir em tempo de build, como navegador e servidor negociam a codificação, e o que deixar sem compressão.
Principais Conclusões
- Brotli no nível máximo é a escolha certa para JavaScript e CSS estáticos, porque o custo da compressão lenta é pago uma única vez no build, e não a cada requisição.
- Zstd é adequado para respostas dinâmicas: a Cloudflare mediu que ele comprime 42% mais rápido que o Brotli, chegando próximo dele em tamanho, com médias de 2,56:1 para gzip, 2,86:1 para zstd e 3,08:1 para Brotli.
- O ajuste de nível importa tanto quanto o nome do algoritmo: migrar para Brotli mantendo um nível padrão rápido desperdiça parte da economia.
- O
gzip_comp_leveldo nginx tem padrão 1 e obrotli_comp_leveldo ngx_brotli tem padrão 6; nenhum dos dois padrões é o máximo. - A negociação de codificação acontece inteiramente nos headers
Accept-EncodingeContent-Encoding, então trocar de algoritmo é uma configuração de servidor ou CDN, não uma alteração no código da aplicação.
Brotli vs Gzip vs Zstd: A Recomendação
Brotli é o padrão correto para JavaScript e CSS estáticos, porque a compressão acontece uma única vez no build, de modo que os níveis mais altos e lentos não custam nada em tempo de requisição.
O gzip permanece na stack como fallback porque todo navegador o lista em Accept-Encoding; não é a melhor escolha para nada, mas é a única escolha que nunca falha.
Zstd é indicado para respostas dinâmicas, nas quais o custo de compressão é pago a cada requisição, porque atinge uma taxa próxima à do Brotli usando uma fração do tempo de CPU.
Estático vs Dinâmico: Quando o Custo da Compressão é Pago?
A decisão entre algoritmos é, na prática, uma decisão sobre quando o custo de CPU é pago. Um bundle estático é comprimido uma vez por release e servido milhares de vezes, então só a taxa de compressão importa e a velocidade é irrelevante. Uma página HTML renderizada por requisição é comprimida a cada acesso, então a velocidade de compressão passa a ser uma preocupação de latência e custo de servidor tão relevante quanto a taxa.
O anúncio do suporte a Zstandard pela Cloudflare em setembro de 2024 fornece os números para o caso dinâmico. Em um teste no Q3 de 2024 que trocou o tráfego do plano Free de Brotli para zstd durante 24 horas, com zstd no nível padrão 3 e sem indicação dos níveis de Brotli e gzip, o zstd comprimiu 42% mais rápido que o Brotli e chegou perto de igualá-lo em tamanho. As médias medidas foram 2,56:1 para gzip, 2,86:1 para zstd e 3,08:1 para Brotli, e a própria conclusão da Cloudflare foi que o zstd é mais adequado para respostas dinâmicas, incluindo HTML.
Veredito: Brotli vence onde a taxa de compressão é o único critério, o que descreve todo asset estático. Zstd vence onde a compressão roda a cada requisição.
O Nível de Compressão Importa Tanto Quanto o Algoritmo
Todo algoritmo expõe um ajuste de nível, e o nível frequentemente altera mais o resultado do que trocar de algoritmo. O Brotli opera nos níveis 0 a 11 no ngx_brotli, o gzip vai de 1 a 9 no nginx, e a CLI do zstd vai de 1 a 19 com padrão 3, além de 20 a 22 atrás da flag --ultra.
O quanto o ajuste altera o resultado depende inteiramente da entrada, então números publicados de “antes e depois” para o bundle de outra pessoa dizem muito pouco sobre o seu. Passe seus arquivos pela ferramenta de comparação de compressão, que comprime com gzip, Brotli e zstd em qualquer nível diretamente no navegador, e compare os tamanhos você mesmo.
Os padrões explicam por que tantos sites entregam conteúdo na ponta mais rápida da escala. O gzip_comp_level do nginx tem padrão 1, o mais rápido entre os níveis 1 a 9. O brotli_comp_level do ngx_brotli tem padrão 6, numa escala de 0 a 11. Nenhum é o máximo, e ambos são sensatos para compressão on-the-fly, que é exatamente o contexto errado para assets estáticos. Qualquer nível escolhido é pago do lado de quem comprime. O próprio README do projeto zstd destaca que a decodificação roda aproximadamente na mesma velocidade independentemente do nível que produziu o arquivo, o que também vale para zlib e lzma.
Veredito: defina o nível deliberadamente. Máximo para tudo que for pré-comprimido; um nível intermediário para tudo que for comprimido por requisição.
Como Pré-Comprimir Assets em Tempo de Build?
Pré-comprimir significa que a etapa de build grava um arquivo irmão .br, .gz e opcionalmente .zst ao lado de cada asset, e o servidor escolhe o arquivo correspondente sem comprimir nada em tempo de requisição.
brotli -q 11 app.js # writes app.js.br; source kept by default
gzip -9 -k app.js # writes app.js.gz; -k keeps the source
zstd -19 app.js # writes app.js.zst; source kept by default
A CLI do brotli preserva as entradas por padrão e aceita -q para qualidade de 0 a 11. O GNU gzip precisa de -k ou --keep para manter o original no lugar.
Servir os arquivos irmãos no nginx exige duas diretivas:
load_module modules/ngx_http_brotli_static_module.so;
http {
gzip_static on; # serves .gz siblings; module needs --with-http_gzip_static_module
brotli_static on; # serves .br siblings; default off
gzip_vary on; # adds Vary: Accept-Encoding; default off
}
O ngx_http_gzip_static_module não é compilado por padrão. O brotli_static vem do ngx_brotli. O nginx não possui um módulo zstd próprio; o módulo de terceiros zstd-nginx-module adiciona uma diretiva zstd_static para arquivos irmãos .zst, desativada por padrão, e sem ela o zstd para arquivos estáticos é uma configuração no lado da CDN.
As CDNs repassam arquivos pré-comprimidos. A documentação de compressão da Cloudflare afirma que ela mantém o content-encoding: br ou gzip da origem quando o navegador do visitante o suporta e nenhuma funcionalidade de reescrita de resposta (Rocket Loader, Email Address Obfuscation, Polish e outras) está habilitada; ela faz requisições às origens com accept-encoding: br, gzip, portanto zstd vindo da origem não é repassado. O comportamento Brotli Support da Akamai serve e armazena em cache conteúdo Brotli comprimido na origem, retorna variantes não-Brotli para clientes que não aceitam br, e não realiza compressão na borda.
Veredito: comprima em tempo de build, no nível máximo, e configure o servidor ou a CDN para servir o arquivo irmão.
Como Navegador e Servidor Combinam uma Codificação?
O navegador lista as codificações que consegue decodificar em Accept-Encoding, o servidor ou a CDN escolhe uma e rotula a resposta com Content-Encoding, e nenhum código de aplicação participa dessa troca.
GET /app.3f2a1b.js HTTP/1.1
Accept-Encoding: gzip, deflate, br, zstd
HTTP/1.1 200 OK
Content-Encoding: br
Vary: Accept-Encoding
br é o token do Brotli no registro de content-coding do HTTP, definido na RFC 7932 seção 13. O Vary: Accept-Encoding instrui caches compartilhados a não entregarem um corpo Brotli a um cliente que pediu apenas gzip; o nginx o omite a menos que gzip_vary on esteja configurado.
Para ver o que um site entrega hoje, requisite um arquivo de bundle e leia o único header que retorna:
curl -sI -H 'Accept-Encoding: gzip, br, zstd' https://example.com/app.js | grep -i content-encoding
A mesma informação aparece no painel Network do DevTools, em response headers.
O suporte dos navegadores define a ordem de fallback. O Brotli é suportado por todos os principais navegadores atuais. O caniuse lista o zstd no Chrome e Edge a partir da versão 123, Firefox a partir da 126, Opera a partir da 109 e Safari a partir da 26, com o Safari desktop marcado como suporte parcial. Qualquer navegador que omita zstd do Accept-Encoding simplesmente recebe Brotli ou gzip. Não há modo de falha, apenas um fallback.
Veredito: trocar de algoritmo é uma mudança de configuração, e a cadeia de fallback torna isso seguro.
O Que Não Deve Ser Comprimido?
Recomprimir JPEG, MP4 ou WOFF2 desperdiça CPU nas duas pontas para uma economia praticamente nula, porque esses formatos já são comprimidos internamente. O WOFF2 é o caso mais evidente: a RFC 7932 seção 1.2 registra que o formato que ela define está embutido no WOFF 2.0, então um arquivo .woff2 já é saída de Brotli.
Respostas muito pequenas são a outra exclusão. Abaixo de algumas dezenas de bytes, o overhead da codificação supera a economia, razão pela qual o gzip_min_length do nginx e o brotli_min_length do ngx_brotli têm padrão de 20 bytes, e a Cloudflare comprime apenas respostas de no mínimo 48 bytes para gzip e 50 para Brotli e zstd.
As conclusões aqui se aplicam aos arquivos que os navegadores efetivamente recebem, de alguns kilobytes a alguns megabytes. Benchmarks mostrando o Brotli levando minutos vêm de arquivos de centenas de megabytes que nunca trafegam via Content-Encoding.
Veredito: comprima texto (HTML, CSS, JavaScript, JSON, SVG); pule mídia, fontes e respostas minúsculas.
Conclusão
A questão do algoritmo tem uma resposta consolidada: Brotli no nível máximo para tudo que for comprimido em tempo de build, zstd para tudo que for comprimido por requisição, gzip como o piso que todo cliente entende. A questão do nível é a que a maioria das stacks nunca se fez. Rode a verificação com curl no seu próprio bundle, observe a codificação e infira o nível a partir do tamanho; se a resposta for gzip em um padrão rápido, pré-comprimir com Brotli está a uma etapa de build e duas diretivas de servidor de distância.
FAQs
Por que meu site ainda envia gzip quando o navegador suporta Brotli?
Três causas explicam quase todos os casos. Primeiro, Chrome e Firefox só anunciam 'br' em Accept-Encoding sobre HTTPS, então requisições em HTTP puro, incluindo a maior parte do desenvolvimento em localhost, recorrem ao gzip. Segundo, o servidor não tem nenhum módulo Brotli carregado; o nginx precisa do ngx_brotli, que não vem embutido. Terceiro, uma CDN como a Cloudflare repassa uma resposta da origem já rotulada como content-encoding: gzip em vez de transcodificá-la para Brotli.
Qual é a diferença entre 'deflate' e 'gzip' no Content-Encoding?
Ambos carregam o mesmo algoritmo DEFLATE da RFC 1951 e diferem apenas no invólucro. No HTTP, 'deflate' significa o formato zlib da RFC 1950 (cabeçalho de dois bytes, checksum Adler-32) e 'gzip' significa o contêiner da RFC 1952 com um trailer CRC-32. Servidores e navegadores antigos às vezes enviavam DEFLATE bruto sob o nome 'deflate', forçando os clientes a adivinhar, então o gzip se tornou a escolha confiável e o deflate raramente vale a pena ser oferecido.
O Node.js suporta compressão Brotli e zstd nativamente?
Sim. O módulo nativo node:zlib implementa as content-encodings gzip, deflate, br e zstd sem pacotes de terceiros. O Brotli está disponível desde o Node.js 11.7.0 através de zlib.brotliCompress e zlib.createBrotliCompress; o Zstandard chegou no Node.js 23.8.0 com zlib.zstdCompress e zlib.createZstdCompress, e o Node.js 24.6.0 adicionou suporte a dicionários nas APIs de zstd (https://nodejs.org/en/blog/release/v24.6.0). Middlewares de compressão anteriores a essas versões podem ainda negociar apenas gzip, então verifique o content-encoding que eles emitem.