Aplicações local-first: trabalhar offline é fácil, sincronizar sem perder dados é a parte difícil

Desenvolvedora trabalhando em um notebook em uma cabana, com documentos digitais ao redor

Uma aplicação offline-first consegue sobreviver à falta de rede. Uma aplicação local-first vai além: o dispositivo mantém uma cópia primária dos dados, lê e escreve localmente e sincroniza em segundo plano. A diferença parece pequena até duas pessoas editarem a mesma informação em aparelhos desconectados.

Mostrar uma tela sem internet é um problema de cache. Permitir trabalho real, preservar alterações e reconciliar cópias independentes transforma o navegador em parte de um sistema distribuído. É aí que o local-first deixa de ser uma melhoria de loading e vira arquitetura.

PWA, offline-first e local-first não são sinônimos

Uma PWA descreve capacidades de distribuição e integração, como instalação e service worker. Offline-first costuma manter o servidor como fonte de verdade, usando cache e filas para atravessar quedas de conexão. No local-first, a interface não espera confirmação remota para cada leitura ou escrita; o banco local é parte central do produto.

O trabalho original da Ink & Switch associa esse modelo a sete ideais, entre eles resposta rápida, funcionamento entre dispositivos, colaboração, longevidade, privacidade e controle dos dados pelo usuário. Não é uma receita fechada nem a promessa de eliminar servidores. O servidor pode continuar cuidando de sincronização, backup, autenticação e regras que precisam de autoridade central.

Onde os dados ficam no navegador

localStorage resolve preferências pequenas, mas é síncrono e limitado. IndexedDB oferece armazenamento estruturado e assíncrono. O Origin Private File System, ou OPFS, fornece arquivos privados ao origin e acesso otimizado, inclusive operações síncronas dentro de Web Workers. Isso tornou viável executar bancos como SQLite no navegador em cenários mais sérios.

Mesmo assim, armazenamento local não é um disco garantido para sempre. Quotas variam, o usuário pode limpar os dados do site e políticas de eviction podem agir sob pressão de espaço. A aplicação precisa solicitar persistência quando apropriado, acompanhar uso e manter uma estratégia de recuperação.

Sincronização é o produto invisível

Imagine que Ana altere o título de uma tarefa no celular sem rede enquanto Bruno muda o prazo no notebook. Uma mesclagem por campo pode preservar as duas alterações. Se ambos mudarem o título, o sistema precisa escolher uma regra, como last-write-wins, ou apresentar o conflito.

CRDTs são estruturas criadas para que mudanças concorrentes possam convergir sem coordenação central. Funcionam especialmente bem em colaboração de texto e outros domínios com operações combináveis. Não eliminam toda decisão: uma mesclagem matematicamente válida pode produzir um resultado sem sentido para o negócio.

Dois usuários reservarem o último item do estoque é um conflito semântico. Os registros podem se combinar perfeitamente e ainda violar a regra “não vender mais do que existe”. Pagamentos, inventário escasso e permissões críticas normalmente exigem uma autoridade central e consistência mais forte.

Local não significa automaticamente privado

Guardar a cópia principal no dispositivo pode aumentar controle e resiliência, mas a aplicação ainda pode enviar telemetria, sincronizar dados sem criptografia adequada ou expor informações a outros usuários do aparelho. Também precisa resolver logout, revogação de acesso e remoção de dados já replicados.

  • Defina quais dados são locais, replicados e exclusivamente centrais.
  • Modele conflitos por tipo de dado, não com uma regra universal.
  • Projete exportação e recuperação antes de confiar no armazenamento local.
  • Trate autenticação e autorização separadamente da disponibilidade offline.
  • Teste reconexão, duplicação, mudanças fora de ordem e aparelhos com relógios divergentes.

Onde faz sentido começar

Editores, notas, ferramentas de campo e fluxos criativos se beneficiam de resposta imediata e trabalho sem rede. Dashboards cujos dados nascem no servidor, catálogos gigantes e operações transacionais fortes tendem a ser candidatos piores.

Também não é necessário converter o produto inteiro. Rascunhos locais, uma fila confiável de mutações ou um módulo colaborativo podem adotar princípios local-first dentro de uma aplicação tradicional. Essa fatia permite aprender sobre sincronização sem transformar cada tela em um experimento de sistemas distribuídos.

A experiência rápida chama atenção, mas o valor duradouro está em o usuário continuar dono do trabalho quando a rede ou o serviço falha. Para entregar isso, o spinner some da interface e reaparece, com muito mais responsabilidade, no desenho da sincronização.

Sincronize operações, não apenas a fotografia final

Um op-log local guarda cada mudança com ID, entidade, base conhecida e instante lógico. O cliente envia operações pendentes, recebe confirmações e depois busca mudanças remotas desde o último cursor. Repetir a mesma operação não pode aplicar o efeito duas vezes.

{
  "operationId": "device-7:1842",
  "entityId": "note-42",
  "baseVersion": 18,
  "type": "replace-title",
  "value": "Planejamento",
  "deviceTime": "2026-06-03T12:10:00Z"
}

deviceTime ajuda na interface, mas não deve decidir sozinho quem vence: relógios divergem. Use versão do servidor, vetor, CRDT ou uma regra de domínio conforme o tipo de dado. Um título pode aceitar last-write-wins; saldo e permissão exigem operação validada no servidor.

Exclusão precisa sobreviver ao offline

Apagar a linha no servidor faz um aparelho offline reaparecer com a cópia antiga. Tombstones mantêm a informação de exclusão até que todos os clientes relevantes tenham avançado além dela. A coleta desses marcadores precisa de uma política baseada em cursores ou retenção, não apenas idade.

O transporte pode usar polling, SSE ou WebSocket; nenhum deles resolve conflito. A entrega de mudanças locais pode combinar outbox e identificadores idempotentes para sobreviver a repetição.

  • mostre estado pendente, sincronizado e em conflito;
  • permita exportar ou recuperar dados antes de limpar armazenamento;
  • no logout, remova chaves, filas e dados privados do usuário anterior;
  • teste dois aparelhos editando e excluindo a mesma entidade;
  • simule semanas offline e uma migração de schema.

Fontes