A compressão HTTP no Node.js reduz o tamanho de respostas de texto, JSON, CSS, JavaScript e outros formatos antes do envio ao cliente. Menos bytes significam downloads mais rápidos, menor consumo de banda e melhor aproveitamento de conexões lentas. Porém, comprimir tudo indiscriminadamente pode aumentar CPU, atrasar respostas pequenas e causar problemas de cache ou segurança.
O cliente informa algoritmos aceitos por meio de Accept-Encoding. O servidor escolhe uma codificação, como gzip, Brotli ou deflate, e responde com Content-Encoding. A representação comprimida precisa ser tratada como uma variante, normalmente com Vary: Accept-Encoding.
Neste guia, você aprenderá negociação de conteúdo, gzip, Brotli, streams, limites, cache, ETag, proxies, arquivos estáticos, segurança, testes e observabilidade.
Como funciona a compressão HTTP?
O cliente pode enviar:
Accept-Encoding: br, gzip, deflateO servidor escolhe uma opção:
Content-Encoding: br
Vary: Accept-EncodingO RFC 9110 sobre semântica HTTP descreve negociação e Content-Encoding. A documentação oficial do módulo Zlib apresenta gzip, deflate e Brotli no Node.js.
Para fundamentos, consulte Zlib no Node.js. Para respostas em fluxo, veja Streams no Node.js.
Quando comprimir?
Compressão costuma funcionar bem em:
- JSON;
- HTML;
- CSS;
- JavaScript;
- XML;
- SVG;
- texto;
- logs e relatórios textuais.
Arquivos como JPEG, PNG, MP4, ZIP e PDF frequentemente já possuem compressão. Recomprimi-los consome CPU com pouco ganho.
Tamanho mínimo
Respostas pequenas podem aumentar de tamanho ou sofrer latência desnecessária. Defina um threshold:
const MINIMUM_COMPRESSION_SIZE = 1024;O valor ideal depende do tráfego, CPU e algoritmo. Meça com dados reais.
Gzip com node:zlib
const { createGzip } = require('node:zlib');
const { pipeline } = require('node:stream');
function sendGzip(req, res, source) {
res.setHeader('Content-Encoding', 'gzip');
res.setHeader('Vary', 'Accept-Encoding');
pipeline(
source,
createGzip(),
res,
error => {
if (error) {
res.destroy(error);
}
}
);
}Pipeline propaga erros e encerra streams adequadamente.
Brotli
const {
createBrotliCompress,
constants
} = require('node:zlib');
const compressor = createBrotliCompress({
params: {
[constants.BROTLI_PARAM_QUALITY]: 4
}
});Brotli pode gerar arquivos menores que gzip em conteúdo textual, mas níveis altos consomem mais CPU. Para respostas dinâmicas, use qualidade moderada.
Escolhendo o algoritmo
function selectEncoding(header = '') {
const accepted = header
.split(',')
.map(item => item.trim().split(';')[0]);
if (accepted.includes('br')) return 'br';
if (accepted.includes('gzip')) return 'gzip';
return 'identity';
}Esse parser simplificado não trata corretamente valores de qualidade q. Em produção, use uma biblioteca testada ou implemente a gramática completa.
Valores q
Accept-Encoding: gzip;q=1.0, br;q=0.8, identity;q=0.5O cliente expressa preferência. q=0 significa não aceitar aquela codificação.
identity
identity significa sem compressão. Se o cliente rejeita todas as opções e identity possui q=0, o servidor pode retornar 406 Not Acceptable.
Middleware de compressão
Frameworks costumam oferecer middleware que negocia algoritmos e filtra tipos. Mesmo assim, configure:
- threshold;
- níveis;
- tipos permitidos;
- exclusão de streams sensíveis;
- Vary;
- monitoramento de CPU.
Exemplo com Express
const compression = require('compression');
app.use(compression({
threshold: 1024,
level: 6
}));Não use nível máximo por padrão. O ganho adicional costuma ser pequeno em relação ao custo.
Resposta JSON
JSON grande comprime bem por repetir nomes de propriedades. Porém, antes de comprimir, reduza o payload:
- remova campos desnecessários;
- use paginação;
- evite objetos duplicados;
- não envie logs ou debug;
- selecione apenas colunas necessárias.
Veja Paginação em APIs Node.js.
Streaming
Compressão em stream evita carregar o corpo inteiro na memória:
await pipeline(
fileStream,
createGzip(),
response
);O compressor mantém buffers internos e respeita backpressure.
Flush
Em Server-Sent Events ou respostas incrementais, buffers de compressão podem atrasar mensagens. Talvez seja necessário fazer flush ou desabilitar compressão.
Consulte Server-Sent Events com Node.js.
SSE e compressão
Eventos pequenos e frequentes podem ficar presos no compressor. Para baixa latência, envie Cache-Control: no-cache e considere excluir text/event-stream do middleware.
WebSocket
WebSocket possui extensão própria de compressão, como permessage-deflate. A configuração HTTP comum não se aplica depois do upgrade.
Arquivos estáticos pré-comprimidos
Durante o build, gere:
app.js
app.js.gz
app.js.brO servidor ou CDN seleciona a variante sem gastar CPU em cada requisição.
Assets com hash
Combine pré-compressão com nomes imutáveis:
app.a8f391c2.jsIsso permite cache longo e evita recompressão.
Content-Length
Ao comprimir dinamicamente, o tamanho final geralmente não é conhecido antes do término. Remova Content-Length do corpo original e use transferência em chunks ou deixe o runtime definir.
Vary: Accept-Encoding
Sem Vary, uma CDN pode entregar gzip a cliente que não aceita ou armazenar a variante sem compressão para todos.
res.setHeader('Vary', 'Accept-Encoding');Ao adicionar Vary, preserve valores anteriores, como Origin.
ETag
Uma resposta gzip possui bytes diferentes da versão Brotli. Um ETag forte pode variar por codificação. Alternativas:
- ETag específico por variante;
- ETag fraco baseado no conteúdo semântico;
- geração feita pelo proxy;
- checksum do arquivo pré-comprimido.
Veja ETag e Cache HTTP no Node.js.
Cache-Control
Compressão não define validade. Continue configurando Cache-Control conforme o conteúdo.
CDN e proxy reverso
Muitas arquiteturas comprimem no Nginx, CDN ou load balancer. Isso reduz CPU do Node.js e centraliza a política.
Evite compressão dupla. Se o upstream já envia Content-Encoding, o proxy não deve comprimir novamente.
HTTP/2 e HTTP/3
Protocolos modernos comprimem headers, não o corpo automaticamente. Gzip ou Brotli continuam úteis para conteúdo.
Consulte HTTP/2 no Node.js.
CPU e thread pool
Operações assíncronas de zlib podem usar o thread pool da libuv. Compressão intensa compete com filesystem, DNS e criptografia que utilizam o mesmo pool.
Monitorando saturação
Observe:
- CPU;
- event loop delay;
- latência p95 e p99;
- fila do thread pool;
- tempo de compressão;
- bytes antes e depois;
- algoritmo selecionado.
Veja Performance Hooks no Node.js.
Compressão adaptativa
Sob alta CPU, a aplicação pode reduzir qualidade ou deixar o proxy assumir. Evite mudanças imprevisíveis sem métricas e testes.
Nível gzip
Níveis maiores gastam mais CPU. Para respostas dinâmicas, nível intermediário costuma equilibrar custo e tamanho. Para assets no build, níveis maiores são aceitáveis.
Brotli quality
Qualidades altas de Brotli são caras. Use-as apenas na pré-compressão de arquivos estáticos.
Tipos MIME
Comprima apenas tipos conhecidos. Não confie em extensão fornecida pelo usuário. Use o Content-Type real da resposta.
BREACH e dados sensíveis
Compressão de respostas que misturam segredo e entrada controlada pelo atacante pode permitir ataques de canal lateral. Proteções:
- não refletir entrada próxima a tokens;
- usar tokens por requisição;
- desabilitar compressão em respostas sensíveis;
- aplicar SameSite e CSRF;
- evitar segredos no corpo.
Respostas autenticadas
Dados privados podem ser comprimidos, mas avalie riscos e configure cache como private ou no-store.
Bombas de descompressão
Ao receber conteúdo comprimido, limite o tamanho descompactado. Um corpo pequeno pode expandir para gigabytes.
Uploads comprimidos
Não aceite Content-Encoding arbitrário sem parser seguro. Defina:
- tamanho comprimido;
- tamanho descomprimido;
- tempo máximo;
- algoritmos aceitos;
- proporção máxima.
Erros de stream
Se a compressão falha após headers enviados, não é possível trocar para JSON de erro. Destrua a conexão e registre o incidente.
AbortController
Quando o cliente desconecta, cancele a origem e feche o compressor. Veja AbortController no Node.js.
Testes
Cubra:
- cliente com Brotli;
- cliente apenas gzip;
- identity;
- q=0;
- resposta abaixo do threshold;
- tipo já comprimido;
- Vary;
- ETag por variante;
- stream grande;
- cliente desconectado.
Teste gzip
test('comprime JSON com gzip', async () => {
const response = await request('/api/report', {
headers: {
'accept-encoding': 'gzip'
}
});
assert.equal(
response.headers['content-encoding'],
'gzip'
);
});Benchmark
Compare:
- sem compressão;
- gzip em níveis diferentes;
- Brotli em qualidades moderadas;
- compressão no proxy;
- assets pré-comprimidos.
Meça CPU e latência, não apenas tamanho.
Erros comuns
- Comprimir tudo: CPU aumenta sem ganho.
- Usar nível máximo: respostas dinâmicas ficam lentas.
- Esquecer Vary: cache entrega variante errada.
- Manter Content-Length original: resposta fica corrompida.
- Comprimir SSE: eventos atrasam.
- Dupla compressão: proxy e app processam o mesmo corpo.
- Ignorar dados sensíveis: canais laterais se tornam possíveis.
Boas práticas
- Defina threshold.
- Filtre tipos MIME.
- Prefira Brotli ou gzip conforme cliente.
- Use qualidade moderada em conteúdo dinâmico.
- Pré-comprima assets.
- Configure Vary.
- Alinhe ETags.
- Monitore CPU e taxa de compressão.
- Desabilite em streams de baixa latência.
- Limite descompressão de entrada.
Conclusão
A compressão HTTP no Node.js reduz o volume de respostas e melhora desempenho em redes, principalmente para JSON e texto. Gzip possui ampla compatibilidade, enquanto Brotli pode entregar tamanhos menores.
A melhor estratégia combina threshold, tipos adequados, níveis moderados, Vary e cache correto. Para assets, pré-compressão no build ou CDN economiza CPU. Com benchmarks e proteção para dados sensíveis, a compressão melhora capacidade sem criar um novo gargalo no servidor.




