O Que Você Nunca Deve Armazenar em Cache
O cache é uma das melhores ferramentas para performance no frontend. Quando bem utilizado, elimina requisições de rede redundantes, reduz a carga no servidor e faz com que sua aplicação pareça instantânea. Quando mal utilizado, vaza dados privados, serve conteúdo autenticado desatualizado ou prende usuários em estados de página corrompidos dos quais não conseguem escapar.
A regra é mais simples do que a maioria dos artigos sugere: assets estáticos, versionados e públicos são quase sempre seguros para um cache agressivo. Todo o resto deve ser tratado como inseguro por padrão.
Antes de entrar no que não deve ser armazenado em cache, é útil ser preciso sobre onde o cache acontece, pois essas camadas se comportam de maneiras muito diferentes.
Principais Conclusões
- As camadas de cache (HTTP, CDN, Service Worker, bfcache, Web Storage) têm escopos e características de segurança distintos; tratá-las de forma intercambiável causa bugs reais.
no-storeimpede o armazenamento por completo, enquantono-cacheapenas força a revalidação antes da reutilização.- Respostas autenticadas ou específicas de usuário devem utilizar
Cache-Control: private, no-storepara evitar que caches compartilhados vazem dados entre usuários. - Nunca armazene JWTs, refresh tokens ou chaves de API em
localStorageouIndexedDB; prefira cookiesHttpOnly. - Adote
no-storecomo padrão sempre que uma cópia desatualizada ou compartilhada puder causar um problema de segurança ou de integridade.
As Camadas de Cache Não São Intercambiáveis
A maioria dos erros de cache começa com o tratamento dessas camadas como se fossem a mesma coisa:
- Cache HTTP/do navegador — Controlado pelos cabeçalhos
Cache-Controlconforme a RFC 9111. Compartilhado entre abas, persiste entre sessões. - Cache CDN/compartilhado — Fica entre seus usuários e sua origem. Serviços como o Cloudflare CDN armazenam respostas em cache para todos os usuários, não apenas para um.
- Service Worker Cache API — Um cache programável que você controla inteiramente via JavaScript. Persiste até ser explicitamente limpo.
- bfcache (cache de navegação para frente/trás) — Um snapshot em memória de uma página completa feito pelo navegador, utilizado ao navegar de volta. Separado do cache HTTP.
localStorage/sessionStorage— Armazenamento de chave-valor. Sem gerenciamento de expiração, totalmente acessível ao JavaScript.IndexedDB— Armazenamento persistente estruturado. Mesma superfície de exposição a XSS que olocalStorage.
Cada um possui características distintas de persistência, escopo e segurança. Confundi-los causa bugs reais.
Uma Nota Rápida sobre a Semântica do Cache-Control
Essa distinção é amplamente mal reportada, por isso vale a pena deixá-la clara:
no-storesignifica que o cache não deve armazenar a resposta de forma alguma.no-cachenão significa “não armazene em cache.” Significa que a cópia em cache deve ser revalidada com o servidor antes de ser reutilizada.
Se você quiser que uma resposta nunca seja armazenada em lugar algum, use no-store. Se quiser garantir a atualização a cada uso, mas ainda se beneficiar da eficiência de requisições condicionais (ETags, respostas 304), use no-cache.
O Que Você Nunca Deve Armazenar em Cache
Respostas de API Autenticadas e HTML Específico de Usuário
Qualquer resposta que varie conforme a identidade do usuário — HTML de dashboards, páginas de conta, respostas de API contendo dados de perfil — não deve ser armazenada em um cache compartilhado. Um padrão comum é:
Cache-Control: private, no-store
no-store impede que caches HTTP normais armazenem a resposta, enquanto private instrui explicitamente caches compartilhados, como CDNs, a não armazená-la para múltiplos usuários.
Sem private, uma CDN como o Cloudflare pode armazenar em cache uma resposta retornada para o Usuário A e servi-la ao Usuário B. Isso já causou incidentes reais de exposição de dados.
Qualquer Coisa Dentro de um Service Worker que Exija Autenticação
A Service Worker Cache API é efetivamente um proxy de rede programável. Um service worker intercepta todas as requisições fetch dentro do seu escopo. Se você armazenar em cache de forma indiscriminada respostas de API autenticadas ou HTML específico de usuário, esses dados persistirão no armazenamento da Cache API — potencialmente entre sessões, e acessíveis ao service worker independentemente do estado de login.
// ❌ Não faça isso
self.addEventListener('fetch', event => {
event.respondWith(
fetch(event.request).then(response => {
return caches.open('v1').then(cache => {
cache.put(event.request, response.clone());
return response;
});
})
);
});
Esse padrão armazena tudo em cache de forma indiscriminada. Exclua rotas autenticadas explicitamente:
// ✅ Armazene em cache apenas assets públicos e versionados
const CACHEABLE = ['/shell.html', '/app-abc123.js', '/styles-f9c.css'];
self.addEventListener('fetch', event => {
const url = new URL(event.request.url);
if (url.origin !== self.location.origin) return; // ignora cross-origin
if (!CACHEABLE.includes(url.pathname)) return; // deixa ir para a rede
event.respondWith(
caches.match(event.request).then(r => r || fetch(event.request))
);
});
Segredos em localStorage ou IndexedDB
O OWASP HTML5 Security Cheat Sheet é explícito: armazenar JWTs, refresh tokens, chaves de API ou tokens de redefinição de senha em localStorage ou IndexedDB é arriscado, pois qualquer vulnerabilidade XSS no seu domínio pode lê-los. Esses mecanismos de armazenamento não possuem expiração nativa, nenhum equivalente ao HttpOnly e nenhum isolamento em relação a scripts injetados.
Use cookies HttpOnly, Secure e SameSite para tokens sempre que possível. Se precisar usar localStorage, esteja ciente de que está aceitando esse risco.
Discover how at OpenReplay.com.
Respostas Específicas de Usuário na Camada de CDN
Um erro comum no Cloudflare CDN: esquecer a diretiva private em respostas que incluem dados do usuário, ou configurar incorretamente o Vary.
# ❌ Sem private — a CDN pode armazenar isso em cache para todos os usuários
Cache-Control: max-age=300
Content-Type: application/json
# ✅ Correto
Cache-Control: private, no-store
Se sua resposta varia legitimamente com base em um cabeçalho de requisição (como Accept-Language ou Authorization), você deve incluir um cabeçalho Vary correto. Sem ele, uma CDN pode servir uma resposta em cache no idioma francês para um usuário que fala inglês ou, pior ainda, uma resposta autenticada em cache para um usuário não autenticado.
Fique atento também ao envenenamento de cache via parâmetros não incluídos na chave de cache: se sua CDN armazena em cache com base na URL, mas sua aplicação lê um parâmetro de query ou cabeçalho não validado para construir a resposta, um atacante pode envenenar o cache com uma requisição manipulada.
Páginas que Não Devem Sobreviver ao bfcache
Navegadores baseados no Chromium, em versões recentes, tornaram-se mais propensos a usar o bfcache mesmo em páginas que enviam Cache-Control: no-store. Esse comportamento depende do navegador e não é universalmente consistente entre Chrome, Firefox e Safari. O caniuse mostra amplo suporte ao bfcache nos navegadores modernos, mas a interação com no-store difere entre engines e versões de navegador.
Se você tiver páginas onde restaurar um snapshot em memória desatualizado é genuinamente inseguro — como uma página exibindo uma confirmação de pagamento único ou um estado sensível à sessão — use o evento pageshow para detectar restaurações do bfcache e recarregar ou revalidar:
window.addEventListener('pageshow', event => {
if (event.persisted) {
// Página foi restaurada do bfcache
window.location.reload();
}
});
Não assuma que no-store desativa universalmente o bfcache em todos os navegadores.
O Modelo Mental Padrão
Se você não tem certeza se algo deve ser armazenado em cache, pergunte-se: servir uma cópia desatualizada ou compartilhada disso poderia causar um problema de segurança ou de integridade? Se sim, adote Cache-Control: no-store como padrão e trabalhe a partir daí.
Assets estáticos com URLs com hash de conteúdo — seus bundles de JavaScript, arquivos CSS, imagens — podem ser armazenados em cache por um ano com max-age=31536000, immutable. Tudo que estiver vinculado a uma sessão de usuário, estado de autenticação ou operação sensível não pode.
Conclusão
Cache agressivo é um recurso. Cache acidental das coisas erradas é uma vulnerabilidade. A disciplina está em saber qual camada você está utilizando, o que cada diretiva do Cache-Control realmente garante e quais categorias de dados nunca devem sair do controle restrito da origem. Trate qualquer coisa vinculada a um usuário, uma sessão ou um segredo como não armazenável em cache por padrão, e reserve o cache agressivo para os assets estáticos e versionados para os quais ele foi projetado.
Perguntas Frequentes
É possível, mas não recomendado. Qualquer vulnerabilidade XSS no seu domínio dá a um atacante acesso completo de leitura ao localStorage, o que significa que ele pode roubar o token e se passar pelo usuário. Cookies HttpOnly, Secure e SameSite são mais seguros porque o JavaScript não consegue lê-los diretamente. Se você precisar usar localStorage, estará aceitando o risco de XSS e deverá investir fortemente em CSP e sanitização de entradas.
no-store proíbe que caches HTTP normais armazenem a resposta para reutilização. no-cache permite que a resposta seja armazenada, mas exige revalidação com a origem antes de cada reutilização, normalmente por meio de requisições condicionais com ETag ou Last-Modified. Use no-store para dados sensíveis e no-cache quando quiser garantias de atualização, mas ainda se beneficiar de respostas 304 Not Modified.
Geralmente porque a resposta não possui a diretiva private ou tem um cabeçalho Vary incorreto. Sem private, um cache compartilhado trata a resposta como armazenável em cache para todos. Sem Vary em cabeçalhos como Authorization ou Cookie, a CDN ignora esses cabeçalhos ao gerar chaves de cache e pode servir a resposta do Usuário A ao Usuário B. Sempre envie Cache-Control: private, no-store em respostas específicas de usuário.
Não existe um cabeçalho confiável entre navegadores que desative o bfcache. Cache-Control: no-store funciona em alguns navegadores, mas de forma inconsistente. A abordagem confiável é escutar o evento pageshow e verificar event.persisted. Se for verdadeiro, a página foi restaurada do bfcache e você pode forçar um recarregamento ou buscar novamente o estado sensível. Isso é essencial para confirmações de pagamento e páginas pós-logout.
Understand every bug
Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data. Check our GitHub repo and join the thousands of developers in our community.