Um bug de cache que só aparecia em produção: Vite, SRI e cache imutável
Como um arquivo com o mesmo nome e conteúdo diferente poderia bloquear o site de quem voltasse após uma publicação, e a correção que manteve a verificação de integridade.
Blog posts are only available in Portuguese for now.
Em segurança de front-end, duas boas práticas podem entrar em conflito. A primeira é o cache longo e imutável para arquivos com hash no nome. A segunda é a SRI (Subresource Integrity), que faz o navegador recusar um arquivo cujo conteúdo não confere com o esperado. Este texto descreve um caso em que a combinação das duas, em um projeto Nuxt, criava uma falha que só apareceria em produção.
O cenário
O Nuxt serve os arquivos JavaScript em /_nuxt/ com o hash do conteúdo no nome e, por padrão, cache de um ano (immutable). A lógica é correta: se o conteúdo muda, o nome muda, e o navegador baixa o arquivo novo. A biblioteca de segurança do projeto (nuxt-security) acrescenta a SRI: a página declara o hash esperado de cada arquivo e o navegador o confere antes de executar.
Comparação entre antes e depois da correção do cache: com cache imutável, um arquivo com o mesmo nome e conteúdo diferente bloqueava a página; com revalidação a cada carga, a página abre.
O problema
O Vite calcula o hash do nome de um chunk antes de preencher a lista de dependências dele (__vite__mapDeps). Na prática, isso permite que dois builds produzam um arquivo com o mesmo nome e conteúdo diferente.
A verificação foi feita compilando o projeto, alterando um valor de CSS, compilando de novo e comparando nomes e conteúdos em .output/public/_nuxt. Um dos arquivos manteve o nome com o conteúdo alterado.
O efeito só atingiria quem já tinha visitado o site antes da publicação, o que torna o defeito difícil de notar em desenvolvimento e em visitas novas.
As opções
Duas saídas foram descartadas:
- Desligar a SRI. Remove o bloqueio, mas também a proteção contra um arquivo alterado no caminho.
- Manter o cache imutável. Mantém a janela de risco de um ano.
A correção
A solução foi reduzir o cache dos arquivos de /_nuxt/ a 1 segundo, configurado no hook nitro:config do nuxt.config.ts. O navegador passa a revalidar a cada carga: se o arquivo não mudou, o servidor responde 304 e nada é baixado de novo; se mudou, o arquivo novo chega.
hooks: {
'nitro:config'(nitroConfig) {
for (const asset of nitroConfig.publicAssets ?? []) {
if (asset?.baseURL?.startsWith('/_nuxt')) asset.maxAge = 1
}
},
},
Como reproduzir o teste
- Compilar o projeto (
pnpm build). - Alterar uma dependência ou um valor de CSS.
- Compilar novamente.
- Comparar os nomes e os conteúdos dos arquivos em
.output/public/_nuxt.
Se algum nome se repetir com conteúdo diferente, o problema está presente. O procedimento ficou registrado em docs/SECURITY.md, para ser repetido a cada mudança relevante de versão do Vite ou do Nuxt.