Armazenamento para a Web

Há muitas opções diferentes para armazenar dados no navegador. Qual é o melhor para suas necessidades?

As conexões de Internet podem ser instáveis ou inexistentes em qualquer lugar. Por isso, o suporte off-line e o desempenho confiável são recursos comuns em apps Web progressivos. Mesmo em ambientes sem fio perfeitos, o uso criterioso de armazenamento em cache e outras técnicas de armazenamento pode melhorar substancialmente a experiência do usuário. Há várias maneiras de armazenar em cache os recursos estáticos do aplicativo (HTML, JavaScript, CSS, imagens etc.) e os dados (dados do usuário, notícias etc.). Mas qual é a melhor solução? Quanto você pode armazenar? Como evitar que ele seja removido?

Qual devo usar?

Confira uma recomendação geral para armazenar recursos:

O IndexedDB, o OPFS e a API Storage de Cache são compatíveis com todos os navegadores mais recentes. Eles são assíncronos e não bloqueiam a linha de execução principal, mas também há uma variante síncrona do OPFS que está disponível exclusivamente em workers da Web. Eles podem ser acessados no objeto window, em workers da Web e em service workers, o que possibilita o uso deles em qualquer lugar do código.

E quanto a outros mecanismos de armazenamento?

Há vários outros mecanismos de armazenamento disponíveis no navegador, mas eles têm uso limitado e podem causar problemas de desempenho significativos.

SessionStorage é específico da guia e tem como escopo o tempo de vida dela. Pode ser útil para armazenar pequenas quantidades de informações específicas da sessão, como uma chave do IndexedDB. Ele precisa ser usado com cuidado, porque é síncrono e bloqueia a linha de execução principal. Ele é limitado a cerca de 5 MB e pode conter apenas strings. Como é específico da guia, ele não pode ser acessado por workers da Web ou de serviço.

Evite usar o LocalStorage, porque ele é síncrono e vai bloquear a linha de execução principal. Ele é limitado a cerca de 5 MB e pode conter apenas strings. O LocalStorage não pode ser acessado por workers da Web ou de serviço.

Os cookies têm suas utilidades, mas não devem ser usados para armazenamento. Os cookies são enviados com cada solicitação HTTP. Portanto, armazenar mais do que uma pequena quantidade de dados aumentará significativamente o tamanho de cada solicitação da Web. Eles são síncronos e não podem ser acessados por workers da Web. Assim como LocalStorage e SessionStorage, os cookies são limitados apenas a strings.

A API File System Access foi projetada para permitir que os usuários leiam e editem arquivos no sistema de arquivos local. O usuário precisa conceder permissão antes que uma página possa ler ou gravar em qualquer arquivo local, e as permissões não são mantidas entre as sessões, a menos que um identificador de arquivo seja armazenado em cache no IndexedDB. A API File System Access é mais adequada para casos de uso como editores, em que você precisa abrir um arquivo, modificá-lo e, possivelmente, salvar as mudanças de volta no arquivo.

A API File System e a API FileWriter fornecem métodos para ler e gravar arquivos em um sistema de arquivos em sandbox. Embora seja assíncrona, ela não é recomendada porque está disponível apenas em navegadores baseados no Chromium.

Quanto posso armazenar?

Em resumo, muito, pelo menos algumas centenas de megabytes e, possivelmente, centenas de gigabytes ou mais. As implementações do navegador variam, mas a quantidade de armazenamento disponível geralmente é baseada na quantidade de armazenamento disponível no dispositivo.

  • O Chrome permite que o navegador use até 80% do espaço total em disco. Uma origem pode usar até 60% do espaço total em disco. Use a API StorageManager para determinar a cota máxima disponível. Outros navegadores baseados no Chromium podem ser diferentes.
    • No modo de navegação anônima, o Chrome reduz a quantidade de armazenamento que uma origem pode usar para aproximadamente 5% do espaço total em disco.
    • Se o usuário tiver ativado a opção "Limpar cookies e dados de sites quando você fechar todas as janelas" no Chrome, a cota de armazenamento será significativamente reduzida para um máximo de aproximadamente 300 MB.
  • O Firefox permite que o navegador use até 50% do espaço livre em disco. Um grupo eTLD+1 (por exemplo, example.com, www.example.com e foo.bar.example.com) pode usar até 2 GB. Use a API StorageManager para determinar quanto espaço ainda está disponível.
  • O Safari (para computadores e dispositivos móveis) parece permitir cerca de 1 GB. Quando o limite é atingido, o Safari solicita ao usuário que aumente o limite em incrementos de 200 MB. Não encontrei nenhuma documentação oficial sobre isso.
    • Se um PWA for adicionado à tela inicial no Safari para dispositivos móveis, ele vai criar um novo contêiner de armazenamento, e nada será compartilhado entre o PWA e o Safari para dispositivos móveis. Depois que a cota é atingida para um PWA instalado, não parece haver uma maneira de solicitar mais armazenamento.

No passado, se um site excedesse um determinado limite de dados armazenados, o navegador pedia ao usuário permissão para usar mais dados. Por exemplo, se a origem usasse mais de 50 MB, o navegador solicitaria que o usuário permitisse o armazenamento de até 100 MB e, em seguida, perguntaria novamente em incrementos de 50 MB.

Hoje, a maioria dos navegadores mais recentes não solicita o usuário e permite que um site use até a cota alocada. A exceção parece ser o Safari, que solicita permissão para aumentar a cota alocada quando ela é excedida. Se uma origem tentar usar mais do que a cota alocada, as tentativas de gravar dados vão falhar.

Como posso verificar quanto espaço de armazenamento está disponível?

Em muitos navegadores, é possível usar a API StorageManager para determinar a quantidade de armazenamento disponível para a origem e quanto armazenamento ela está usando. Ela informa o número total de bytes usados pelo IndexedDB e pela API Cache, além de possibilitar o cálculo do espaço de armazenamento restante aproximado.

if (navigator.storage && navigator.storage.estimate) {
  const quota = await navigator.storage.estimate();
  // quota.usage -> Number of bytes used.
  // quota.quota -> Maximum number of bytes available.
  const percentageUsed = (quota.usage / quota.quota) * 100;
  console.log(`You've used ${percentageUsed}% of the available storage.`);
  const remaining = quota.quota - quota.usage;
  console.log(`You can write up to ${remaining} more bytes.`);
}

É preciso capturar erros de cota excedida (veja abaixo). Em alguns casos, é possível que a cota disponível exceda a quantidade real de armazenamento disponível.

Inspecionar

Durante o desenvolvimento, use o DevTools do navegador para inspecionar os diferentes tipos de armazenamento e limpar todos os dados armazenados.

Um novo recurso foi adicionado ao Chrome 88 que permite substituir a cota de armazenamento do site no painel "Armazenamento". Esse recurso permite simular dispositivos diferentes e testar o comportamento dos apps em cenários de baixa disponibilidade de disco. Acesse Aplicativo e Armazenamento, marque a caixa de seleção Simular cota de armazenamento personalizada e insira qualquer número válido para simular a cota de armazenamento.

Como lidar com o excesso de cota?

O que fazer quando você ultrapassa a cota? O mais importante é sempre capturar e processar erros de gravação, sejam eles um QuotaExceededError ou outro tipo. Em seguida, dependendo do design do app, decida como lidar com isso. Por exemplo, exclua conteúdo que não é acessado há muito tempo, remova dados com base no tamanho ou ofereça uma maneira de os usuários escolherem o que querem excluir.

O IndexedDB e a API Cache geram um DOMError chamado QuotaExceededError quando você excede a cota disponível.

IndexedDB

Se a origem exceder a cota, as tentativas de gravação no IndexedDB vão falhar. O gerenciador onabort() da transação será chamado, transmitindo um evento. O evento vai incluir um DOMException na propriedade de erro. Verificar o erro name vai retornar QuotaExceededError.

const transaction = idb.transaction(['entries'], 'readwrite');
transaction.onabort = function(event) {
  const error = event.target.error; // DOMException
  if (error.name == 'QuotaExceededError') {
    // Fallback code goes here
  }
};

API Cache

Se a origem exceder a cota, as tentativas de gravação na API Cache serão rejeitadas com um QuotaExceededError DOMException.

try {
  const cache = await caches.open('my-cache');
  await cache.add(new Request('/sample1.jpg'));
} catch (err) {
  if (error.name === 'QuotaExceededError') {
    // Fallback code goes here
  }
}

Como funciona a remoção?

O armazenamento na Web é categorizado em dois grupos: "Melhor resultado" e "Persistente". O melhor esforço significa que o armazenamento pode ser limpo pelo navegador sem interromper o usuário, mas é menos durável para dados de longo prazo ou críticos. O armazenamento permanente não é limpo automaticamente quando o armazenamento está baixo. O usuário precisa limpar manualmente esse armazenamento (pelas configurações do navegador).

Por padrão, os dados de um site (incluindo IndexedDB, API Cache etc.) se enquadram na categoria de melhor esforço, o que significa que, a menos que um site tenha solicitado armazenamento persistente, o navegador pode remover os dados do site a seu critério, por exemplo, quando o armazenamento do dispositivo está baixo.

A política de remoção para o melhor esforço é:

  • Os navegadores baseados no Chromium começam a remover dados quando ficam sem espaço, limpando todos os dados do site da origem menos usada recentemente primeiro, depois da próxima, até que o navegador não esteja mais acima do limite.
  • O Firefox começa a remover dados quando o espaço em disco disponível é preenchido, limpando todos os dados do site da origem menos usada recentemente primeiro, depois a seguinte, até que o navegador não esteja mais acima do limite.
  • O Safari não removia dados, mas recentemente implementou um novo limite de sete dias em todo o armazenamento gravável (veja abaixo).

A partir do iOS e iPadOS 13.4 e do Safari 13.1 no macOS, há um limite de sete dias para todo o armazenamento gravável de script, incluindo o IndexedDB, o registro de service worker e a API Cache. Isso significa que o Safari vai remover todo o conteúdo do cache após sete dias de uso se o usuário não interagir com o site. Essa política de remoção não se aplica a PWAs instalados que foram adicionados à tela inicial. Consulte Bloqueio total de cookies de terceiros e muito mais no blog do WebKit para ver todos os detalhes.

Buckets de armazenamento

A ideia principal da API Storage Buckets é permitir que os sites criem vários buckets de armazenamento, em que o navegador pode excluir cada bucket de forma independente. Isso permite que os desenvolvedores especifiquem a priorização de remoção para garantir que os dados mais valiosos não sejam excluídos.

Bônus: por que usar um wrapper para o IndexedDB

O IndexedDB é uma API de baixo nível que requer uma configuração significativa antes do uso, o que pode ser particularmente difícil para armazenar dados de baixa complexidade. Ao contrário da maioria das APIs modernas baseadas em promessas, ela é baseada em eventos. Wrappers de promessa como idb para IndexedDB ocultam alguns dos recursos avançados, mas, o mais importante, ocultam o maquinário complexo (por exemplo, transações, controle de versões de esquema) que vem com a biblioteca IndexedDB.

Bônus: SQLite Wasm

Depois que o Web SQL foi descontinuado e removido do Chrome, o Google trabalhou com os mantenedores do conhecido banco de dados SQLite para oferecer uma substituição para o Web SQL com base no SQLite. Leia SQLite Wasm no navegador com suporte do sistema de arquivos particular de origem para saber como usá-lo.

Conclusão

O armazenamento limitado e a necessidade de pedir ao usuário para armazenar cada vez mais dados ficaram no passado. Os sites podem armazenar de forma eficaz todos os recursos e dados necessários para serem executados. Usando a API StorageManager, é possível determinar quanto espaço está disponível e quanto você já usou. E com o armazenamento persistente, a menos que o usuário o remova, é possível protegê-lo contra remoção.

Outros recursos

Obrigado

Agradecemos especialmente a Jarryd Goodman, Phil Walton, Eiji Kitamura, Daniel Murphy, Darwin Huang, Josh Bell, Marijn Kruisselbrink e Victor Costan por revisarem este guia. Agradecemos a Eiji Kitamura, Addy Osmani e Marc Cohen, que escreveram os artigos originais em que este se baseia. Eiji escreveu uma ferramenta útil chamada Browser Storage Abuser, que foi útil para validar o comportamento atual. Ele permite que você armazene o máximo de dados possível e veja os limites de armazenamento no navegador. Agradecemos a François Beaufort, que pesquisou o Safari para descobrir os limites de armazenamento, e a Thomas Steiner, que adicionou informações sobre o sistema de arquivos particular de origem, os buckets de armazenamento, o SQLite Wasm e uma atualização geral de conteúdo em 2024.