Uma landing page criada por IA pode guardar tema, etapa de formulário, parâmetros de campanha ou até uma resposta inteira de API sem explicar por quanto tempo esses dados permanecem. A página continua funcionando, mas o navegador acumula estado que ninguém inventariou — e uma republicação não apaga automaticamente o que já foi salvo na origem.

Revisar armazenamento no navegador é descobrir o que o JavaScript grava, por que grava, quem consegue ler e quando cada valor é removido. O objetivo não é proibir estado local. É evitar que conveniência vire persistência indefinida, dado sensível acessível a qualquer script da página ou comportamento diferente entre preview, URL padrão e domínio próprio.

Este guia cobre localStorage, sessionStorage, IndexedDB, Cache Storage e cookies observáveis pelo frontend antes e depois da publicação.

Comece pelo inventário, não pela limpeza

Antes de apagar dados, procure as APIs e bibliotecas responsáveis na fonte:

Terminal window
rg -n \
'localStorage|sessionStorage|indexedDB|caches\.|document\.cookie|cookieStore|persist\(' \
src public

Amplie os caminhos conforme a estrutura real do projeto. Depois registre cada item:

chave ou banco:
API de armazenamento:
origem do valor:
finalidade:
formato:
contém dado pessoal ou credencial:
quem lê:
quando expira ou é removido:
comportamento quando está ausente ou inválido:

Busque também wrappers de estado, SDKs de Analytics, widgets e scripts externos. Uma chamada pode aparecer como storage.set(), persist() ou configuração de biblioteca sem citar a API do navegador diretamente. O guia de scripts de terceiros ajuda a rastrear código que executa na mesma página.

Diferencie as áreas de armazenamento

A Web Storage API da MDN distingue dois mecanismos síncronos:

  • sessionStorage é separado por origem e aba e costuma desaparecer quando a aba é fechada;
  • localStorage é separado por origem e persiste entre sessões do navegador.

IndexedDB atende estruturas e volumes maiores com operações assíncronas. Cache Storage guarda pares de requisição e resposta, normalmente em fluxos offline ou com Service Worker. Cookies seguem outro contrato: podem ser enviados automaticamente ao servidor conforme domínio, caminho e atributos.

Não escolha a API apenas pelo tamanho. Para cada dado, compare:

Necessidade Pergunta de revisão
preferência que pode persistir a pessoa espera reencontrar esse valor?
etapa temporária de formulário precisa sobreviver ao fechamento da aba?
resposta de API pode ficar desatualizada ou conter dado pessoal?
sessão autenticada o frontend deveria conseguir ler a credencial?
asset offline existe estratégia de atualização e remoção do cache?
experimento ou campanha a chave tem versão, expiração e finalidade documentadas?

Armazenar por padrão é uma decisão de retenção. Se o valor pode ser recalculado, solicitado novamente ou mantido somente em memória, talvez não precise ocupar uma área persistente.

Não guarde segredos no Web Storage

localStorage e sessionStorage são acessíveis ao JavaScript executado naquela origem. Uma falha de XSS ou um script de terceiro comprometido pode ler os valores, e o próprio código pode sobrescrevê-los.

O HTML5 Security Cheat Sheet da OWASP recomenda não guardar informação sensível nem identificadores de sessão em armazenamento local. Isso inclui:

  • token de acesso ou refresh token;
  • chave de API privada;
  • senha ou segredo temporário;
  • conteúdo integral de formulário com dados pessoais;
  • resposta de API que a pessoa não espera encontrar depois;
  • sinal usado sozinho para conceder autorização.

Mover um token de localStorage para sessionStorage reduz persistência, mas não impede leitura por JavaScript na aba. Para sessões controladas por um backend, cookies HttpOnly, Secure e com SameSite adequado reduzem a exposição ao JavaScript, mas exigem um contrato de servidor e revisão de CSRF. Uma landing estática não deve inventar esse backend no frontend.

O guia para evitar chaves no frontend mostra como separar configuração pública de credencial privada antes de gerar o pacote.

Trate tudo que volta do navegador como entrada não confiável

O estado local não é uma fonte autenticada. A pessoa pode editar valores no DevTools; extensões, outro script da origem ou uma versão antiga da página também podem alterá-los.

Ao ler um item:

  1. trate ausência como um caso normal;
  2. limite tamanho antes de fazer parse;
  3. valide tipo, campos e valores permitidos;
  4. descarte versões desconhecidas;
  5. use defaults seguros;
  6. não transforme o valor em HTML ou código;
  7. nunca conceda acesso porque uma flag local diz admin=true.

Um exemplo simples para preferência visual pode usar um conjunto fechado:

const savedTheme = localStorage.getItem("theme");
const theme = savedTheme === "light" || savedTheme === "dark" ? savedTheme : "light";
document.documentElement.dataset.theme = theme;

O valor controla uma preferência conhecida. Ele não entra em innerHTML, não escolhe uma URL arbitrária e não decide autorização.

Defina versão, expiração e migração

localStorage não oferece expiração automática por item. Se um dado precisa vencer, salve metadados e aplique a regra na leitura:

const parsed = JSON.parse(localStorage.getItem("campaign:v2") ?? "null");
if (!parsed || parsed.expiresAt <= Date.now()) {
localStorage.removeItem("campaign:v2");
}

O exemplo ainda precisa de validação estrutural antes de usar outros campos. A ideia principal é tornar o ciclo de vida explícito.

Prefira chaves com namespace e versão, como landing:theme:v1, e mantenha uma tabela de migração:

Versão anterior Nova versão Ação ao carregar
ausente v1 usar default, sem gravar à força
v1 válida v2 transformar campos conhecidos
v1 inválida v2 remover e reconstruir
desconhecida atual ignorar ou limpar com segurança

Não execute migração destrutiva sem saber quais páginas compartilham a origem. Todos os documentos da mesma origem podem enxergar o mesmo localStorage, independentemente do caminho.

Teste cada origem que continuará ativa

Origem combina protocolo, host e porta. Por isso, estas URLs possuem áreas de armazenamento diferentes:

https://campanha.sitenoar.app
https://www.campanha.com.br
http://localhost:4321

Um domínio próprio adiciona outro endereço ao Site, enquanto a URL padrão continua disponível. O conteúdo pode ser o mesmo, mas localStorage, IndexedDB e cookies não se tornam um repositório compartilhado entre os hosts.

Valide separadamente:

  • primeira visita sem estado;
  • retorno com estado válido;
  • valor corrompido ou de versão antiga;
  • navegação em outra aba;
  • modo privado, quando relevante;
  • URL padrão e cada domínio próprio mantido;
  • republicação sobre uma origem que já possui dados;
  • opção da pessoa para limpar ou redefinir preferências.

Não copie dados entre origens com query string, fragmento ou postMessage sem um contrato específico. Essa solução pode expor valores na URL ou ampliar o fluxo de confiança.

Inspecione o navegador e a rede

Na build local e depois na URL publicada:

  1. abra DevTools em um perfil limpo;
  2. use Application ou Storage para listar Web Storage, IndexedDB, cookies e caches;
  3. percorra as jornadas da página;
  4. compare o estado antes e depois de formulário, consentimento e integração;
  5. recarregue, feche a aba e abra novamente;
  6. edite um valor para confirmar que a aplicação falha de forma segura;
  7. verifique no painel Network se cookies ou dados armazenados chegam a serviços externos;
  8. registre origem, chave, finalidade e resultado.

Use apenas dados sintéticos. Não preencha uma página de teste com informações reais para descobrir depois que o navegador as persistiu.

O artigo de testes de formulário ajuda a revisar estados de envio, erro e confirmação sem confundir armazenamento local com entrega ao sistema receptor.

Publique o artefato revisado

O contrato de Publicação envia HTML, JavaScript e assets preparados pelo projeto. A plataforma não remove chamadas a localStorage, não migra IndexedDB e não limpa o navegador de quem visitou uma versão anterior.

Antes do deploy:

  • remova gravações sem finalidade;
  • elimine valores sensíveis e credenciais;
  • adicione validação, versão e expiração quando necessárias;
  • teste atualização a partir da versão anterior;
  • confirme que a build não inclui um Service Worker obsoleto;
  • gere o pacote final e repita a inspeção nele.

Depois de republicar, abra a URL em um navegador que já visitou a versão anterior e em outro perfil limpo. O primeiro testa compatibilidade; o segundo revela dependências acidentais de estado antigo.

Prompt copiável para o agente

Revise o armazenamento no navegador desta landing page sem apagar dados ainda.
Aplicação publicável: [caminho]
Origem de preview: [URL]
URL padrão: [URL]
Domínios próprios ativos: [lista]
- Inventarie localStorage, sessionStorage, IndexedDB, Cache Storage e cookies.
- Para cada item, informe origem, chave, finalidade, formato, leitores e retenção.
- Marque credenciais, dados pessoais e respostas de API sensíveis.
- Trate todos os valores lidos como entrada não confiável.
- Identifique chaves sem versão, expiração ou remoção.
- Não limpe armazenamento nem migre dados antes da aprovação.
- Teste primeira visita, retorno, valor inválido e republicação.
- Verifique separadamente cada origem publicada.
- Devolva diff proposto, evidências no navegador e riscos restantes.

Checklist final

  • todas as APIs de armazenamento foram buscadas;
  • wrappers e scripts de terceiros entraram no inventário;
  • nenhuma credencial ou segredo fica acessível no Web Storage;
  • dados pessoais têm finalidade e retenção justificadas;
  • valores lidos são validados antes do uso;
  • chaves persistentes têm versão e ciclo de vida;
  • estados ausente, antigo e corrompido foram testados;
  • URL padrão e domínios próprios foram verificados separadamente;
  • formulário e integrações funcionam sem estado antigo;
  • Version publicada corresponde aos arquivos inspecionados;
  • limpeza ou migração destrutiva recebeu aprovação;
  • próxima revisão ficou registrada.

Armazenamento no navegador é parte do comportamento publicado, mesmo quando não aparece na interface. Quando cada valor tem finalidade, escopo, validação e descarte, a landing deixa de depender de uma memória invisível e passa a ter um ciclo de vida que a equipe consegue testar.