Offline first

Escopo: transversal. Aplica-se a qualquer linguagem ou stack do projeto.

Offline-first é a estratégia de design em que o app funciona a partir de dados locais e usa a rede para sincronizar, não o contrário. A premissa é que conectividade é intermitente: metrô, túnel, roaming, sinal fraco. Um app que falha silenciosamente sem rede transfere ao usuário um problema que é responsabilidade do produto.

A diferença em relação a um app com cache simples: no offline-first, o banco local é a fonte de verdade da UI (User Interface, interface do usuário). A rede atualiza o banco; o banco atualiza a UI.

Conceitos fundamentais

ConceitoO que é
Cache (armazenamento em cache)Cópia local de dados remotos para acesso sem rede
Sync (sincronia)Processo de reconciliar dados locais com o servidor
Conflict resolution (resolução de conflito)Estratégia para decidir qual versão vence quando local e remoto divergem
Optimistic update (atualização otimista)Aplicar a mudança localmente antes da confirmação do servidor
Stale-while-revalidate (cache imediato com revalidação ao fundo)Exibir dado em cache enquanto busca versão atualizada em background
Queue (fila de operações)Lista de ações locais pendentes de sincronização com o servidor
idempotency (operação repetível sem efeito adicional)Operação que produz o mesmo resultado independente de quantas vezes é executada
Tombstone (marcador de deleção)Registro de deleção local que o servidor ainda não confirmou

O modelo offline-first

O fluxo padrão inverte a dependência da rede:

UIdo banco local → banco local é atualizado pela rede (em background)

Comparado ao modelo tradicional:

Tradicional:   UI → rede → exibe resultado
Offline-first: UI → banco local → rede atualiza banco → UI reage

No modelo tradicional, a rede está no caminho crítico de toda leitura. No offline-first, a rede está no caminho de atualização: a UI nunca espera por ela para exibir dados.

Cache strategy

A estratégia de cache define quando buscar dados frescos e quando servir do local:

EstratégiaComportamentoQuando usar
Cache-firstServe do local; busca rede em backgroundDados que mudam pouco (catálogo, perfil)
Network-firstTenta rede; fallback para local se falharDados que mudam frequentemente (feed, preços)
Stale-while-revalidateServe local imediatamente; atualiza em backgroundMelhor UX percebida; sempre rápido
Network-onlySempre da rede; sem fallbackDados em tempo real (saldo, disponibilidade)

A maioria dos casos se beneficia de stale-while-revalidate: o usuário vê dados imediatamente e a atualização chega sem spinner.

Optimistic update

Optimistic update aplica a mudança na UI antes de o servidor confirmar. A hipótese é que a operação vai ter sucesso, daí o otimismo.

Usuário curte post → UI mostra curtida imediatamente → requisição vai ao servidor em background
                                                       ↓ falha → reverte curtida na UI + notifica

Regras para usar optimistic update com segurança:

  • A operação deve ser reversível se o servidor rejeitar
  • O usuário deve ser informado se a reversão acontecer
  • Não usar em operações destrutivas sem confirmação (exclusão de conta, transferência financeira)

Fila de operações e sincronização

Quando o usuário age sem rede, as operações precisam ser enfileiradas para sincronização posterior:

Offline: Usuário cria pedido → pedido salvo localmente com status "pendente"
Online:  Fila processada → pedido enviado ao servidor → status atualizado para "confirmado"

A fila deve ser persistente: se o app for encerrado com operações pendentes, elas devem sobreviver ao cold start e ser processadas quando a rede voltar.

Cada operação na fila deve ser idempotente: se enviada duas vezes (por nova tentativa após falha de rede), o resultado deve ser o mesmo. A estratégia mais comum é incluir um ID único gerado pelo cliente em cada operação.

Conflict resolution

Conflito ocorre quando local e remoto divergem: o usuário editou um registro offline enquanto outro usuário o editou no servidor.

EstratégiaComportamentoTrade-off
Last write winsQuem salvou por último venceSimples; pode perder dados
Server winsVersão do servidor sempre prevalecePrevisível; descarta mudanças locais
Client winsVersão local sempre prevaleceFavorece o usuário; pode criar inconsistências
MergeTentativa de combinar as duas versõesMelhor resultado; complexo de implementar
ManualApresenta o conflito ao usuário para resolverCorreto; intrusivo

A estratégia depende do domínio. Para notas pessoais, merge é o ideal. Para transações financeiras, server wins é o mais seguro. Nunca escolher a estratégia sem entender o custo de cada tipo de conflito para o usuário.

Network-aware UX

O estado da rede deve ser visível e comunicado sem alarmar:

EstadoComportamento esperado
ConectadoFluxo normal; sem indicadores desnecessários
DesconectadoBanner discreto informando modo offline; funcionalidades disponíveis claras
ReconectadoSincronia automática em background; notificação apenas se relevante
Operação pendenteIndicador de "aguardando sincronia" na entidade afetada
Sincronia falhouMensagem clara com opção de nova tentativa; nunca perder a operação silenciosamente

O erro mais comum é mostrar uma tela de erro onde deveria aparecer dado em cache. Se o dado existe localmente, exibi-lo. A rede é um detalhe de implementação, não um estado de erro do produto.

Desenvolvido por @thiagocajadev · Fork baseado no repositório pmndrs/docs · Poimandres.