---
term: 'Sincronização de Dados Offline-First'
seoTitle: 'Sincronização Offline-First: Store Local, Conflitos e Replay'
headline: 'O que é sincronização de dados offline-first?'
slug: sincronizacao-offline-first
category: api-realtime
shortDefinition: 'A sincronização offline-first é uma arquitetura em que o app lê e escreve primeiro em um store local e só depois reconcilia com o servidor.'
relatedTerms:
  - real-time-live-queries
  - websockets-real-time-sync
  - push-notifications-apns-fcm
  - cross-platform-development
contrastsWith:
  - real-time-live-queries
faq:
  - question: 'O que é um app offline-first?'
    answer: 'Um app cujas telas leem e escrevem em um banco de dados no próprio dispositivo, com a rede rebaixada a um detalhe de sincronização. Todo toque funciona em modo avião porque nada no caminho da interação espera pelo servidor; uma camada de sync em segundo plano reconcilia o store local com o backend sempre que a conexão permite. O contrário são os apps online-first, que exibem spinners até o servidor responder.'
  - question: 'Como funciona a sincronização de dados offline?'
    answer: 'Em dois loops. Saída: as escritas locais caem no store do dispositivo e em uma fila de outbox durável; quando a rede volta, a fila é reexecutada contra o servidor, em ordem. Entrada: o cliente puxa as mudanças desde o último sync — por timestamp, número de sequência ou change feed — e as aplica localmente. A resolução de conflitos fica onde os dois loops se encontram, decidindo o que acontece quando os dois lados editaram o mesmo registro.'
  - question: 'O que é last-write-wins e quando ele é seguro?'
    answer: 'A política de conflito mais simples: compare timestamps ou versões e mantenha a escrita mais nova, descartando a outra em silêncio. É segura quando as edições são de registro inteiro e de baixo risco — um toggle de configuração, uma flag de status — ou quando o normal é ter um único escritor por registro. É perigosa em documentos compartilhados e contadores, onde "descartar a outra edição" significa perder o trabalho de alguém.'
  - question: 'Como os CRDTs resolvem conflitos?'
    answer: 'Tornando o conflito irrepresentável: um CRDT restringe cada campo a operações que fazem merge de forma determinística, independentemente da ordem de chegada — contadores que só crescem, conjuntos de add/remove, registradores de último escritor. Duas réplicas que trocam estado sempre convergem sem coordenação. O custo é disciplina de modelagem: seus dados precisam ser expressos no vocabulário CRDT, e bibliotecas completas são pesadas demais para o app mobile típico de formulários e listas.'
  - question: 'O que acontece com as escritas feitas offline?'
    answer: 'Elas entram na fila. Uma outbox durável registra cada operação pendente, e o store local reflete a escrita imediatamente, para que a interface continue fiel à intenção do usuário. Na reconexão, a fila é reexecutada em ordem; falhas exigem política — reprocessar erros transitórios, mas devolver rejeições de permissão e de validação para a interface em vez de tentar para sempre. Operações idempotentes tornam o replay seguro quando um acknowledgment se perde.'
  - question: 'Offline-first é a mesma coisa que cache?'
    answer: 'Não — a diferença é a direção. Um cache acelera leituras: o servidor continua sendo a fonte da verdade e entradas velhas são apenas rebuscadas. Offline-first também aceita escritas localmente, e é isso que cria divergência entre dispositivo e servidor e obriga a maquinaria pesada — filas de outbox, replay, resolução de conflitos. Cache de leitura é otimização de performance; offline-first é um compromisso de arquitetura de dados.'
  - question: 'Quando não vale a pena construir offline-first?'
    answer: 'Quando a correção depende de um único estado autoritativo: pagamentos, reservas, baixa de estoque, qualquer coisa em que dois dispositivos agindo sobre dados velhos criem gasto em dobro no mundo real. Também quando os dados são calculados no servidor e majoritariamente de leitura — um feed de notícias degrada bem com um cache simples. A maquinaria de sync é um imposto permanente; pague onde as condições de campo ou a expectativa do usuário exigirem.'
  - question: 'Como os SDKs mobile fixam dados localmente?'
    answer: 'Fixar (pin) marca objetos para armazenamento durável no dispositivo: registros fixados sobrevivem a reinícios, podem ser consultados offline pela mesma interface de consulta usada no servidor e permanecem ligados às suas identidades no servidor, para que syncs posteriores os atualizem no lugar. Combinado com escritas enfileiradas — a semântica de save-eventually —, o pinning entrega uma camada offline funcional sem escrever um schema de banco à mão no dispositivo.'
codeLanguages: [javascript, dart, swift, kotlin]
externalAuthorities:
  - name: 'Conflict-free replicated data type (Wikipedia)'
    url: 'https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type'
  - name: 'Eventual consistency (Wikipedia)'
    url: 'https://en.wikipedia.org/wiki/Eventual_consistency'
  - name: 'Version Vector — Patterns of Distributed Systems (martinfowler.com)'
    url: 'https://martinfowler.com/articles/patterns-of-distributed-systems/version-vector.html'
  - name: 'Local Datastore — Parse iOS SDK guide'
    url: 'https://docs.parseplatform.org/ios/guide/#local-datastore'
cta:
  title: 'Publique apps que funcionam em modo avião'
  text: 'Os SDKs mobile do Back4app já vêm com o Local Datastore: fixe (pin) consultas para leitura offline, enfileire escritas com save-eventually e deixe o backend gerenciado ser a fonte da verdade contra a qual seus dispositivos reconciliam.'
  linkText: 'Comece grátis'
  linkUrl: 'https://www.back4app.com/signup'
author: 'Back4app Engineering'
publishedDate: '2026-08-25'
translationKey: offline-first-data-sync
---

**A sincronização offline-first é uma arquitetura em que o app lê e escreve primeiro em um store local e só depois reconcilia com o servidor.** A rede deixa de ser dependência e vira processo de segundo plano — toda tela renderiza a partir do dispositivo, todo toque grava no dispositivo, e uma camada de sync acerta as contas com o backend quando a conexão permite. A parte difícil nunca foi guardar dados localmente; é o que acontece quando duas cópias da verdade discordam.

## Principais pontos

| Pergunta | Resposta |
| --- | --- |
| O movimento central | As telas falam com um store local no dispositivo; a rede sincroniza em segundo plano |
| As duas posturas | Store local como *buffer* (o servidor é dono da verdade) ou como *réplica* (a verdade é negociada) |
| Escritas offline | Fila de outbox durável → replay em ordem na reconexão |
| Conflitos | Última escrita vence · merge por campo · CRDTs — escolha por campo, não por app |
| O custo honesto | A maquinaria de sync é complexidade permanente — gaste onde o offline importa |

## Fixar, ler, enfileirar: o loop offline em código

A versão do padrão nos SDKs mobile — fixe o que as telas precisam, enfileire o que os usuários mudam:

**JavaScript:**

```javascript
// JavaScript — Back4app JS SDK with the Local Datastore enabled
Parse.enableLocalDatastore();

// Read: serve the screen from the local store — instant, works offline
const query = new Parse.Query('Task');
query.fromLocalDatastore();
render(await query.find());

// Write: durable locally now, queued for the server automatically
const task = new Parse.Object('Task');
task.set('title', 'Inspect site 14');
task.set('done', false);
await task.pin();          // survives restarts, feeds local reads
task.saveEventually();     // replays when the network returns
```

**Flutter:**

```dart
// Flutter / Dart — Back4app Flutter SDK with local storage enabled
// Parse().initialize(..., coreStore: await CoreStoreSembastImp.getInstance())

// Read: serve the screen from pinned data — instant, works offline
final pinned = await ParseObject('Task').fromPin('openTasks');
render(pinned);

// Write: durable locally now, synced when the network allows
final task = ParseObject('Task')
  ..set('title', 'Inspect site 14')
  ..set('done', false);
await task.pin();                 // survives restarts, feeds local reads

final response = await task.save();
if (!response.success) retryLater(task);   // replay from the outbox
```

**Swift:**

```swift
// iOS / Swift — Back4app iOS SDK with the Local Datastore enabled
// (in the app delegate) Parse.enableLocalDatastore()

// Read: serve the screen from the local store — instant, works offline
let query = PFQuery(className: "Task")
query.fromLocalDatastore()
query.findObjectsInBackground { tasks, _ in
  render(tasks)
}

// Write: durable locally now, queued for the server automatically
let task = PFObject(className: "Task")
task["title"] = "Inspect site 14"
task["done"] = false
task.pinInBackground()     // survives restarts, feeds local reads
task.saveEventually()      // replays when the network returns
```

**Kotlin:**

```kotlin
// Android / Kotlin — Back4app Android SDK with the Local Datastore enabled
// (before Parse.initialize) Parse.enableLocalDatastore(context)

// Read: serve the screen from the local store — instant, works offline
val query = ParseQuery.getQuery<ParseObject>("Task")
query.fromLocalDatastore()
query.findInBackground { tasks, _ ->
    render(tasks)
}

// Write: durable locally now, queued for the server automatically
val task = ParseObject("Task")
task.put("title", "Inspect site 14")
task.put("done", false)
task.pinInBackground()     // survives restarts, feeds local reads
task.saveEventually()      // replays when the network returns
```

Duas chamadas carregam a arquitetura inteira. *Fixar (pin)* torna objetos duráveis e consultáveis no dispositivo — a mesma interface de consulta do servidor, apontada para o armazenamento local ([Local Datastore](https://docs.parseplatform.org/ios/guide/#local-datastore) nos SDKs acima). *Save-eventually* aceita a escrita agora e assume a entrega depois. Todo o resto deste artigo é o que acontece entre essas duas chamadas.

## Buffer ou fonte da verdade? A decisão por trás da decisão

Todo design offline-first escolhe silenciosamente uma entre duas posturas para o store local, e quase toda dor de sync vem de não ter escolhido de propósito.

**Store local como buffer.** O servidor continua autoritativo; o dispositivo guarda uma cópia de trabalho mais uma outbox de intenção. Conflitos se resolvem a favor do servidor ou por política simples, e um dispositivo sempre pode ser consertado com um novo pull. É o padrão certo para apps de negócio — checklists de CRM, inspeções em campo, entrada de pedidos — porque o raciocínio permanece simples: a verdade mora em um lugar só, os dispositivos apenas ficam atrasados em relação a ela.

**Store local como réplica.** A verdade é *negociada* entre pares e o servidor é uma réplica entre várias — a postura de editores colaborativos e apps de notas que prometem semântica de "faz merge de tudo". Isso compra resiliência e confiança do usuário ao preço de maquinaria de sistemas distribuídos de verdade: version vectors para detectar concorrência, funções de merge por tipo de dado e [consistência eventual](https://en.wikipedia.org/wiki/Eventual_consistency) como a garantia mais forte que dá para prometer honestamente.

A postura se escolhe por *conjunto de dados*, não por app: o mesmo app de serviço em campo pode tratar as ordens de serviço do próprio técnico como réplica (elas precisam ser editáveis o dia todo no subsolo) e o catálogo de peças como buffer read-through.

## O loop de sync, de ponta a ponta

```mermaid
flowchart LR
  accTitle: Loop de sincronização offline-first
  accDescr: O app lê e escreve em um store local. As escritas também entram em uma fila de outbox durável. Quando a conexão volta, a fila reexecuta as operações em ordem no servidor, o servidor aplica resolução de conflitos contra edições concorrentes, e o cliente puxa para o store local as mudanças ocorridas desde o último sync.
  UI["Telas do app"] -->|"ler + escrever"| LS[("Store local")]
  UI -->|"cada escrita"| OB["Fila de outbox<br/>(durável, ordenada)"]
  OB -->|"replay na reconexão"| SV["Servidor<br/>(resolução de conflitos)"]
  SV -->|"mudanças desde o último sync"| LS
  SV --- DB[("Banco de dados do backend")]
```

O loop tem uma metade de saída e uma de entrada, e elas se encontram na resolução de conflitos.

**Saída: escritas enfileiradas e replay.** Escritas offline são efetivadas localmente e anexadas a uma outbox durável — a interface reflete a intenção imediatamente, rotulada honestamente como "pendente" onde isso importa. Na reconexão, a fila é reexecutada em ordem. A engenharia está nos modos de falha: erros transitórios são retentados com backoff; rejeições de permissão e validação precisam *aparecer para o usuário*, não ser retentadas eternamente; e as operações devem ser idempotentes, porque um acknowledgment perdido significa que a mesma operação pode chegar duas vezes. Reexecutar "marcar status como concluído" duas vezes é inofensivo; reexecutar "incrementar estoque em 3" duas vezes é bug.

**Entrada: pull de deltas.** Rebuscar tudo na reconexão desperdiça banda e bateria; sync de produção puxa *mudanças desde um checkpoint* — uma marca d'água de updated-at nos designs simples, um número de sequência do servidor ou um change feed nos mais robustos. Os mesmos eventos que alimentam as [live queries](/glossary/pt/live-queries-tempo-real/) quando o app está online servem também como stream de deltas de entrada, e é por isso que sync offline e sync em tempo real são um contínuo, não duas features.

## Última escrita vence vs. merge vs. CRDTs

| Estratégia | Como resolve | Perde dados? | Custo | Boa para |
| --- | --- | --- | --- | --- |
| Última escrita vence (last-write-wins) | O timestamp/versão mais novo fica com o registro | **Sim — em silêncio** | Trivial | Registros de escritor único, toggles, status |
| Merge por campo | Edições em campos distintos sobrevivem; colisões no mesmo campo caem na política | Só em colisões no mesmo campo | Moderado | Formulários e registros editados por poucas pessoas |
| Resolução manual | Guarda as duas versões; um humano escolhe | Não | Interface + fluxo | Registros de alto risco, consoles de sync |
| CRDT / CRDT-lite | Tipos de dado cujas operações fazem merge de forma determinística | Não | Disciplina de modelagem, peso da biblioteca | Contadores, conjuntos, estruturas colaborativas |

Duas observações honestas sobre essa tabela. Primeira: timestamps são mais frágeis do que parecem — relógios de dispositivo derivam, então um last-write-wins sério usa a hora de recebimento no servidor ou versões lógicas ([version vectors](https://martinfowler.com/articles/patterns-of-distributed-systems/version-vector.html) quando edições concorrentes precisam ser *detectadas* e não adivinhadas). Segunda: o ponto ideal na prática, em mobile, é o **CRDT-lite**: bibliotecas [CRDT](https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type) completas são maquinaria pesada, mas roubar a ideia campo a campo é barato — modele uma quantidade como *operações de incremento* em vez de um total armazenado, tags como *conjuntos* de add/remove em vez de um array, e esses campos simplesmente param de conflitar. A estratégia se escolhe por campo; apps que adotam uma política global costumam ter escolhido errado para algum campo.

## Casos de uso comuns

- **Operações em campo** — inspeções, entregas, serviços de utilidade pública: o local do trabalho é exatamente onde a cobertura morre, então a captura precisa ser local e o sync, oportunista.
- **Notas e produtividade pessoal** — um usuário, vários dispositivos: conflitos são raros e quase sempre autoinfligidos, o que torna as políticas de merge tratáveis.
- **PDV e apps de quiosque** — a fila precisa continuar andando durante o reboot do roteador; as transações são reexecutadas quando o link volta.
- **Apps de viagem** — itinerários, mapas e bilhetes consumidos justamente onde a conexão é pior: aviões, trens, roaming.
- **Produtos para mercados emergentes e baixa banda** — conexões intermitentes e tarifadas fazem do sync incremental em segundo plano o único design respeitoso, seja qual for a [combinação de plataformas](/glossary/pt/desenvolvimento-multiplataforma/).

## Você deveria construir offline-first? Matriz de decisão

| Construa offline-first quando… | Fique online-first quando… |
| --- | --- |
| Os usuários trabalham onde a cobertura falha — campo, trânsito, subsolo | Os usuários estão praticamente sempre conectados (web desktop, ferramentas de escritório) |
| Um toque bloqueado custa dinheiro ou confiança (PDV, captura em campo) | A correção exige um estado autoritativo *agora* — pagamentos, reservas, estoque |
| Os dados são registros do próprio usuário, com poucos escritores concorrentes | Os dados são calculados no servidor ou compartilhados e quentes (feeds, dashboards) — faça cache de leitura e mantenha as escritas online |
| As escritas toleram reconciliação posterior | Dois dispositivos agindo sobre verdade velha é um risco real no mundo físico |
| Você consegue bancar a manutenção permanente da camada de sync | Um estado de carregamento é honestamente suficiente |

O meio-termo é legítimo e comum: faça cache das leituras para telas instantâneas, permita escritas offline só nos poucos conjuntos de dados que realmente precisam, e mantenha o resto online-first atrás de estados de conectividade honestos.

## Limitações e trade-offs

- **Resolução de conflitos é decisão de produto fantasiada de engenharia.** "Qual edição sobrevive?" não tem resposta tecnicamente correta — alguém precisa assumir a política campo a campo, e "manter a mais nova em silêncio" é uma escolha com vítima.
- **A outbox é uma fila de passivos.** Escritas pendentes de um dispositivo que reaparece depois de duas semanas podem ser reexecutadas em um mundo que mudou; políticas de expiração e validação no replay são obrigatórias, não paranoia.
- **Bugs de sync são a pior classe de bug que dá para comprar.** Só se reproduzem sob interleavings específicos de edições e conectividade — invista em testes de replay determinístico e em uma superfície visível de status de sync, ou depure por anedota para sempre.
- **A verdade local envelhece.** As telas precisam ser honestas sobre defasagem onde isso importa (preços, disponibilidade) — "atualizado há 2 horas" é feature, não pedido de desculpas.
- **Armazenamento e identidade pesam.** Stores de dispositivo precisam de migrações como qualquer banco, e registros criados offline precisam de identidades geradas no cliente que sobrevivam à viagem até o servidor sem duplicar.

## Sincronização offline-first no Back4app

O Back4app é uma plataforma open-source de Backend as a Service (BaaS) que combina banco de dados gerenciado, APIs REST e GraphQL geradas automaticamente, autenticação, armazenamento de arquivos e funções serverless com Cloud Code. Seus SDKs mobile entregam a postura de buffer pronta: o Local Datastore fixa os resultados de qualquer consulta para leitura offline pela mesma API de consulta, o `saveEventually` dá a toda escrita uma outbox durável com replay ordenado, e triggers `beforeSave` no Cloud Code são a casa natural da política de conflito no servidor — checagem de versão, regras de merge, validação no replay — aplicada em todo caminho de escrita. Combine com o stream de [Live Query](/glossary/pt/live-queries-tempo-real/) para os deltas de entrada enquanto o app está online, e o loop de sync do diagrama acima vira configuração mais política, não um subsistema para construir.
