Compressão HTTP reduz o tamanho das respostas enviadas pela rede. No Node.js, o módulo node:zlib oferece Gzip, Deflate e Brotli por meio de streams. A compressão pode melhorar tempo de transferência de HTML, CSS, JavaScript, JSON e texto, mas consome CPU e pode ser inútil para imagens e vídeos já comprimidos.
A decisão deve considerar tipo de conteúdo, tamanho, suporte do cliente, cache, latência e capacidade de CPU. Em muitas arquiteturas, CDN ou proxy reverso é o melhor lugar para comprimir respostas públicas.
Accept-Encoding
O cliente informa os formatos aceitos no cabeçalho:
Accept-Encoding: br, gzip, deflateO servidor escolhe um formato e responde:
Content-Encoding: br
Vary: Accept-EncodingVary é importante para que caches diferenciem versões comprimidas e sem compressão.
Servidor com Gzip
import { createServer } from 'node:http';
import { createGzip } from 'node:zlib';
import { Readable } from 'node:stream';
import { pipeline } from 'node:stream/promises';
const server = createServer(async (req, res) => {
const body = JSON.stringify({ items: createItems() });
const acceptsGzip = /\bgzip\b/.test(req.headers['accept-encoding'] || '');
res.setHeader('content-type', 'application/json; charset=utf-8');
res.setHeader('vary', 'Accept-Encoding');
if (!acceptsGzip) {
res.end(body);
return;
}
res.setHeader('content-encoding', 'gzip');
try {
await pipeline(
Readable.from([body]),
createGzip(),
res,
);
} catch (error) {
res.destroy(error);
}
});
server.listen(3000);Esse exemplo demonstra negociação simples. Uma implementação completa precisa analisar qualidade, formatos e tipos de conteúdo.
Brotli
import { createBrotliCompress } from 'node:zlib';
const brotli = createBrotliCompress();Brotli costuma produzir arquivos menores para texto, mas níveis altos podem consumir muita CPU. Para conteúdo estático, comprima durante o build. Para respostas dinâmicas, use configuração moderada e benchmark.
Deflate
Deflate é suportado, mas Gzip e Brotli são opções mais comuns na web. Priorize formatos de acordo com compatibilidade e infraestrutura.
Negociação de qualidade
O cliente pode informar pesos:
Accept-Encoding: br;q=1.0, gzip;q=0.8, identity;q=0.5Não escolha apenas procurando uma substring. Use uma biblioteca de negociação bem testada ou implemente parsing conforme o padrão.
Identity
identity significa resposta sem transformação. Um cliente pode rejeitar certos formatos ou até indicar que não aceita identity. Trate os casos corretamente.
Vary
Sem Vary: Accept-Encoding, um cache pode servir uma resposta Brotli a um cliente que não suporta Brotli, ou armazenar várias versões de forma errada.
Content-Length
O tamanho comprimido não é conhecido antes do processamento em streaming. Remova um Content-Length calculado sobre o corpo original. O Node.js pode usar transferência em chunks ou o protocolo adequado.
Não comprima tudo
Evite compressão para:
- JPEG, PNG, WebP e AVIF;
- MP4 e áudio comprimido;
- ZIP, Gzip e outros arquivos compactados;
- respostas muito pequenas;
- dados criptografados;
- streams em que latência por chunk é crítica.
Comprimir conteúdo já comprimido gasta CPU e pode aumentar tamanho.
Limiar mínimo
Defina um tamanho mínimo, por exemplo alguns centenas de bytes ou mais, após benchmark. Cabeçalhos e custo de CPU podem superar o ganho em corpos pequenos.
if (Buffer.byteLength(body) < 1024) {
res.end(body);
return;
}Tipos de conteúdo
Uma allowlist pode incluir:
- text/html;
- text/css;
- application/javascript;
- application/json;
- application/xml;
- image/svg+xml;
- text/plain.
Normalize o Content-Type e ignore parâmetros como charset ao comparar.
Compressão dinâmica e estática
Conteúdo estático deve ser pré-comprimido durante build ou no CDN:
app.js
app.js.gz
app.js.brO servidor escolhe o arquivo correspondente sem gastar CPU em cada requisição.
Compressão de JSON
JSON repetitivo comprime bem. Entretanto, serializar objetos grandes e comprimir na thread principal pode aumentar event loop delay. Limite tamanho, pagine e considere cache.
Streams
Use pipeline para arquivos grandes:
await pipeline(
createReadStream('export.csv'),
createGzip(),
res,
);O pipeline aplica backpressure e propaga erros.
Flush
Aplicações de streaming podem solicitar flush para enviar dados acumulados, mas flush frequente reduz taxa de compressão e aumenta overhead. Server-Sent Events e respostas progressivas exigem testes específicos.
Nível de compressão
Níveis maiores geralmente reduzem mais o tamanho, com custo de CPU e latência. Para Gzip:
import { createGzip, constants } from 'node:zlib';
const gzip = createGzip({
level: constants.Z_BEST_SPEED,
});Não presuma que melhor compressão é melhor para usuário. Compare tempo total.
Parâmetros do Brotli
Brotli oferece parâmetros de qualidade e modo. Níveis máximos são adequados para build offline, não necessariamente para respostas dinâmicas.
CPU e thread pool
Operações zlib assíncronas usam recursos do runtime e podem competir com outras tarefas. Muitas compressões simultâneas aumentam filas e memória. Monitore saturação e limite concorrência.
Cache
Cacheie variantes por encoding. Uma chave pode incluir URL normalizada, versão e encoding. Não misture respostas personalizadas de usuários.
ETag
Um ETag calculado sobre a representação sem compressão pode ser fraco ou tratado conforme a estratégia do servidor. Garanta consistência entre variantes e condicionais. Proxies podem alterar a representação.
CDN
CDNs normalmente oferecem compressão automática, cache e Brotli. Evite comprimir no Node.js e novamente no proxy. Verifique headers finais.
Proxy reverso
Nginx, Envoy e ingress controllers podem comprimir. Centralizar reduz CPU da aplicação e padroniza políticas. Ainda assim, respostas internas e streaming podem exigir configuração específica.
Segurança
Compressão de respostas que misturam segredo e entrada controlada pelo usuário pode criar canais laterais de tamanho. Reduza riscos:
- não refletir segredos em respostas;
- separar dados sensíveis;
- usar tokens imprevisíveis;
- desativar compressão em páginas críticas quando necessário;
- aplicar políticas de segurança modernas.
Decompression bombs
Ao aceitar conteúdo comprimido, limite tamanho comprimido e descomprimido. Um arquivo pequeno pode expandir para gigabytes.
let decompressedBytes = 0;
transform.on('data', (chunk) => {
decompressedBytes += chunk.length;
if (decompressedBytes > MAX_SIZE) {
transform.destroy(new Error('Conteúdo descomprimido excedeu o limite'));
}
});Observabilidade
Meça:
- bytes antes e depois;
- razão de compressão;
- duração;
- CPU;
- event loop delay;
- formato escolhido;
- respostas abaixo do limiar;
- erros;
- cache hit por variante.
Teste de carga
Compare sem compressão, Gzip e Brotli em diferentes níveis. Meça tempo total do cliente, p95, p99, throughput, CPU e banda. Use payloads reais.
Erros comuns
- comprimir imagens já comprimidas;
- não enviar Vary;
- manter Content-Length original;
- usar Brotli máximo dinamicamente;
- comprimir respostas minúsculas;
- ignorar backpressure;
- comprimir duas vezes;
- não limitar descompressão;
- medir somente tamanho e ignorar CPU.
Fluxo recomendado
Prefira CDN ou proxy para conteúdo público, pré-comprima arquivos estáticos e use compressão dinâmica moderada apenas para tipos e tamanhos adequados. Combine com Streams e Backpressure, Event Loop Utilization, CPU Profiling e testes com Autocannon.
Consulte a documentação oficial de zlib e a referência de Accept-Encoding.




