Cache-Control é um cabeçalho HTTP que define as regras de cache de recursos no cliente, servidores proxy e CDNs usando um conjunto de diretivas. Ao contrário do obsoleto cabeçalho Expires, Cache-Control suporta dezenas de combinações: max-age define o tempo de vida em segundos, private e public controlam a disponibilidade do cache, no-cache e no-store — verificação forçada. De acordo com Google Web Dev (2025), a configuração correta do Cache-Control pode reduzir o tempo de carregamento de páginas em 50-80% para visitas repetidas. Isso torna o cabeçalho criticamente importante para o desempenho de aplicações web e móveis.
Principais conclusões
Cache-Control é um cabeçalho HTTP, padronizado em HTTP/1.1 (RFC 7234), que permite ao servidor especificar como e por quanto tempo clientes, proxies e CDNs podem armazenar em cache a resposta. Ao contrário de Expires (HTTP/1.0), Cache-Control usa diretivas — comandos de texto combinados com vírgulas: Cache-Control: public, max-age=3600, must-revalidate. O cabeçalho fornece controle refinado sobre cada elo da cadeia de cache.
O cache é um dos mecanismos fundamentais de desempenho de aplicações web e móveis. Sem ele, cada solicitação do usuário iria diretamente ao servidor, causando carga excessiva e latência. Cache-Control define três níveis de cache: navegador/aplicativo (cache privado), servidores proxy (cache compartilhado) e CDN (cache distribuído). Cada nível interpreta as diretivas de forma diferente.
A configuração incorreta do Cache-Control é uma das causas mais comuns de problemas de desempenho. Um cache muito agressivo faz com que os usuários vejam dados desatualizados. Um cache muito fraco leva a solicitações excessivas ao servidor e carregamento lento. De acordo com a Akamai (2025), otimizar o Cache-Control para conteúdo estático reduz a carga do servidor em 70-90% e melhora o tempo de carregamento em 40-60% para usuários móveis.
Cache-Control apareceu em HTTP/1.1 (RFC 2616, 1999) como substituto do Expires. O Expires tinha um problema fundamental: usava uma data absoluta que dependia dos fusos horários do servidor e do cliente. O Cache-Control resolveu esse problema mudando para o tempo relativo (max-age em segundos a partir do momento em que a resposta é recebida). Mais tarde, no RFC 7234 (2014), novas diretivas foram adicionadas: immutable para ativos estáticos, stale-while-revalidate e stale-if-error para validação diferida.
Cache-Control inclui mais de 10 diretivas divididas em três grupos: diretivas de solicitação (cliente → servidor), diretivas de resposta (servidor → cliente) e extensões. Na prática, o desenvolvimento móvel usa 6-7 diretivas de resposta principais que cobrem 95% dos cenários de cache. Vamos examinar cada uma com exemplos e recomendações.
| Diretiva | Significado | Exemplo |
|---|---|---|
| max-age | Tempo de vida em segundos a partir da resposta | max-age=3600 — 1 hora |
| s-maxage | max-age para cache compartilhado (proxy, CDN) | s-maxage=86400 — 1 dia para CDN |
| public | Permite armazenamento em cache por todos (incluindo proxies) | public, max-age=3600 |
| private | Permite cache apenas para o navegador/aplicativo | private, max-age=600 |
| no-cache | Não usar sem validação (304 necessário) | no-cache |
| no-store | Proibir completamente o armazenamento em cache | no-store |
| must-revalidate | Após max-age, deve revalidar com a origem | max-age=3600, must-revalidate |
| immutable | O recurso não mudará (para ativos estáticos versionados) | max-age=31536000, immutable |
max-age é a diretiva mais importante. Ela proíbe o cliente de fazer uma solicitação ao servidor pelo tempo especificado. Para ativos estáticos (CSS, JS, imagens), o max-age geralmente é definido de 1 dia a 1 ano. Para respostas de API — de 0 segundos (dados sempre atualizados) a 5-10 minutos (dados de referência). s-maxage permite definir diferentes tempos de vida para CDN e navegador: a CDN armazena uma cópia por 1 dia, o navegador por 1 hora.
Essas duas diretivas são frequentemente confundidas. no-cache não proíbe o cache — ele exige validar a cópia em cache a cada uso por meio de uma solicitação condicional (If-Modified-Since ou If-None-Match). Se o servidor responder com 304 — o cliente usa o cache. Se 200 — ele atualiza. no-store, por outro lado, proíbe completamente salvar a resposta em qualquer cache, incluindo disco e memória. Use no-store apenas para dados confidenciais — tokens, dados de pagamento, documentos pessoais.
O cabeçalho Expires (HTTP/1.0) também especifica o tempo de vida do recurso, mas usa uma data absoluta: Expires: Thu, 03 Jul 2026 12:00:00 GMT. Cache-Control max-age usa tempo relativo a partir do momento da resposta. A diferença é crítica para sistemas distribuídos: se o servidor e o cliente estão em fusos horários diferentes, o Expires pode ser interpretado incorretamente. O Cache-Control não tem esse problema — 3600 segundos são sempre 3600 segundos.
Quando ambos os cabeçalhos estão presentes, Cache-Control tem prioridade sobre Expires. Isso é definido no RFC 7234: “Se uma resposta incluir um campo Cache-Control com a diretiva max-age, o destinatário DEVE ignorar o campo Expires.” Na prática, é recomendado não retornar Expires para clientes modernos, pois Cache-Control cobre todos os cenários de Expires. No entanto, para compatibilidade retroativa com proxies e navegadores antigos, ambos os cabeçalhos podem ser retornados.
Expires sobreviveu principalmente para conteúdo estático em Nginx e Apache — esses servidores adicionam automaticamente ambos os cabeçalhos. Se seu projeto encontrar Expires sem Cache-Control, substitua-o por Cache-Control com max-age: a precisão do controle de cache melhora e a dependência do fuso horário é eliminada. Para migração, basta configurar o servidor para adicionar Cache-Control em vez de Expires.
# Nginx: Cache-Control para arquivos estáticos
location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
expires 30d;
add_header Cache-Control "public, immutable, max-age=2592000";
}
# Políticas diferentes para diferentes tipos de conteúdo
location /api/config {
expires -1;
add_header Cache-Control "no-cache, must-revalidate";
}
location /api/static-data {
expires 5m;
add_header Cache-Control "public, max-age=300";
}
Na configuração do Nginx, arquivos estáticos (CSS, JS, imagens) são definidos com Cache-Control por 30 dias com o atributo immutable — este atributo informa ao navegador que o recurso nunca muda nesta URL (versionamento via hash no nome do arquivo). Os endpoints de API usam no-cache para dados dinâmicos e public com um max-age curto para dados de referência — listas frequentemente solicitadas e raramente alteradas.
Em aplicações móveis, o Cache-Control desempenha um papel especial devido às limitações das redes móveis: alta latência, conexão instável, limites de tráfego. Um cache adequado permite exibir dados ao usuário instantaneamente, mesmo offline, e atualizá-los em segundo plano. OkHttp no Android e URLSession no iOS têm sistemas de cache integrados que respeitam o Cache-Control.
OkHttp usa CacheInterceptor, que lê o Cache-Control da resposta e gerencia automaticamente o cache. Se o servidor retornou Cache-Control: max-age=3600, o OkHttp não fará uma solicitação ao servidor por uma hora. Após a expiração do max-age, o OkHttp envia uma solicitação condicional com If-Modified-Since e If-None-Match. Configuração de cache no OkHttp: OkHttpClient.Builder().cache(Cache(directory, maxSize)).
fun createCachedClient(cacheDir: File): OkHttpClient {
return OkHttpClient.Builder()
.cache(Cache(cacheDir, 10L * 1024 * 1024))
.addNetworkInterceptor { chain ->
val response = chain.proceed(chain.request())
response.newBuilder()
.header("Cache-Control",
"public, max-age=300")
.removeHeader("Pragma")
.build()
}
.build()
}
O código cria um OkHttpClient com cache de 10 MB e substitui o Cache-Control através do NetworkInterceptor. Se o servidor não retornar Cache-Control ou usar Expires, o interceptor adiciona public, max-age=300 (5 minutos). O interceptor remove o cabeçalho obsoleto Pragma (HTTP/1.0) para compatibilidade. O cache no iOS funciona de forma semelhante através do URLCache.shared com as configurações memoryCapacity e diskCapacity.
A diretiva stale-while-revalidate permite mostrar ao usuário um cache obsoleto enquanto o aplicativo busca dados atualizados em segundo plano. Isso proporciona um efeito de resposta instantânea: o usuário vê o conteúdo imediatamente e, após um segundo, ele é atualizado para a versão atual. Suportado pelo OkHttp a partir da versão 3.10 e pelo URLCache no iOS 14+. Exemplo: Cache-Control: max-age=3600, stale-while-revalidate=300 — 1 hora de cache atualizado, depois 5 minutos exibindo dados obsoletos com atualização em segundo plano.
Diferentes tipos de recursos exigem diferentes estratégias de cache. Vamos ver as configurações ideais para cenários típicos no desenvolvimento móvel. Para conteúdo estático com hash no nome do arquivo (bundle.abc123.js), você pode definir max-age de até 1 ano com immutable. Para listas de API que são raramente atualizadas (direórios, categorias) — max-age de 5 minutos a 1 hora com stale-while-revalidate.
| Tipo de recurso | Cache-Control | Explicação |
|---|---|---|
| Ativos estáticos versionados | public, max-age=31536000, immutable | 1 ano, arquivos não mudam (hash na URL) |
| Ativos estáticos não versionados | public, max-age=86400, must-revalidate | 1 dia com revalidação forçada após |
| API: dados de referência | public, max-age=600, stale-while-revalidate=60 | 10 minutos de cache + 1 minuto obsoleto |
| API: dados do usuário | private, max-age=60 | 1 minuto, apenas para um usuário específico |
| API: dados confidenciais | no-store | Proibição completa de cache |
| Páginas HTML | no-cache, must-revalidate | Validação em cada solicitação, 304 se inalterado |
Importante lembrar da segurança: para respostas contendo dados pessoais do usuário, sempre defina private. Sem essa diretiva, um proxy público (por exemplo, corporativo) pode armazenar a resposta em cache e entregá-la a outro usuário. Para tokens de autenticação e informações de pagamento, use no-store — nem mesmo um cache privado deve armazenar esses dados em disco.
Para verificar a correção do Cache-Control, use o cabeçalho Age (quantos segundos o cache foi armazenado) e X-Cache (hit/miss na CDN). No navegador — a guia Network, a coluna Size mostra “from disk cache” ou “304 Not Modified”. Se um recurso deve ser armazenado em cache, mas é carregado toda vez, verifique se o servidor está adicionando Cache-Control: no-cache ou Pragma: no-cache junto com suas diretivas.
Perguntas frequentes
max-age se aplica a todos os caches (incluindo navegadores), s-maxage se aplica apenas a caches compartilhados (proxies, CDNs). Se s-maxage for especificado, a CDN ignora max-age e usa s-maxage. Isso permite definir diferentes tempos de vida para o navegador e a CDN.
Não, após enviar uma resposta com max-age, o cliente não fará uma solicitação até que o temporizador expire. Para invalidação imediata do cache, você precisa alterar a URL do recurso (adicionar versão/hash) e enviar notificações push ou mensagens WebSocket para reinicialização forçada.
A diretiva immutable (RFC 8246) informa ao navegador que o recurso nunca mudará nesta URL. O navegador nem tenta fazer uma solicitação condicional ao atualizar a página — ele usa o cache até a expiração do max-age. Funciona apenas com arquivos versionados.
O Googlebot leva o Cache-Control em consideração: cache longo acelera o rastreamento repetido. noindex com cache rápido é aceitável. no-store pode desacelerar a indexação porque o Googlebot carregará a página do zero toda vez. Um max-age muito curto aumenta a carga do servidor durante o rastreamento.
Através do helmet ou middleware: res.set('Cache-Control', 'public, max-age=3600'). Para arquivos estáticos, use express.static com o parâmetro maxAge: express.static('public', {maxAge: '1y'}). Para rotas dinâmicas — individualmente em cada manipulador.
Resumo
Vamos desenvolver um aplicativo móvel chave na mão
A IT Sectr cria aplicativos para iOS e Android para startups e empresas desde 2017. Nós vamos aconselhá-lo e propor a melhor solução.
Leia também