Aplicações Local-First: O Futuro da Web?

Publicado primeiro na newsletter Dev na Gringa. Se quiser receber o próximo no seu e-mail, se inscreva.


Aplicações web hoje dependem muito de servidores. Todo dado fica em algum datacenter, e precisamos de internet para acessar qualquer coisa.

Porém, existe um movimento crescente para mudar isso: aplicações local-first.

Como funcionam aplicações local-first. Fonte: Palestra do Martin Kleppmann na Local-First Conf em 2024

Como um engenheiro de software focado em produtos, eu sou fascinado por aplicações local-first.

O principal motivo é pela experiência incrível que ela fornece aos nossos usuários.

Esse é um movimento que me interessa principalmente por isso.

A capacidade de criar aplicações web que pareçam como um app nativo.

Com nenhum loading ou skeleton. Que carregue instantaneamente. Interações de UI sendo feitas a 60 FPS.

Hoje, vamos ter um artigo um pouco diferente, mais técnico.

Uma introdução a arquitetura local-first.

✨ O que esperar do artigo

  • O que é desenvolvimento local-first e porque ele está ganhando tração agora
  • Como algumas empresas já usam essa arquitetura com sucesso
  • Uma análise profunda dos trade-offs e quando essa abordagem faz sentido

O que é desenvolvimento local-first

Desenvolvimento local-first é uma mudança de paradigma na forma como construímos aplicações.

Em vez dos dados ficarem centralizados em um servidor, cada cliente (navegador, app móvel) mantém uma cópia local completa dos dados. O servidor passa a ter um papel secundário: ajudar na sincronização entre diferentes dispositivos.

A mudança principal é que o cliente passa a ser a fonte primária da verdade, não mais o servidor.

Algumas aplicações famosas já usam esse conceito:

  • Figma: permite editar designs mesmo offline
  • Linear: toda operação é instantânea pois os dados estão locais
  • Notion: você pode continuar escrevendo mesmo sem internet
  • Superhuman: email funciona perfeitamente offline

Como isso funciona na prática

A tecnologia que permite esse tipo de arquitetura são os CRDTs (Conflict-free Replicated Data Types).

São estruturas de dados especiais que garantem que diferentes cópias dos dados podem ser modificadas independentemente e depois sincronizadas sem conflitos.

Como funcionam Conflict-free Replicated Data Types (CRDTs). Fonte: Demystifying CRDTs: Enhancing Distributed Systems.

Vamos ver um exemplo prático. Imagine um editor de texto colaborativo:

  1. Alice começa a digitar um documento no seu laptop
  2. Bob abre o mesmo documento no computador dele
  3. Alice perde internet mas continua digitando
  4. Bob também faz alterações no documento
  5. Quando Alice reconecta, as mudanças são mescladas automaticamente

Com CRDTs, não precisamos nos preocupar com conflitos de edição. A estrutura de dados garante que as alterações podem ser mescladas de forma determinística.

Algumas aplicações como o Figma e o Linear não usam CRDTs propriamente. Pois, CRDTs são um pouco mais complexos: são feitos para funcionarem de maneira descentralizada.

Como essas aplicações tem uma fonte de verdade final (no servidor), a sua lógica fica um pouco mais simples. Pois não é necessário a questão de descentralização, já que há uma entidade com autoridade central.

Por isso, as sync engines são um pouco mais simples.

Mas, tudo isso começa a partir dos princípios que as CRDTs trazem. Diretamente do blog de engenharia do Figma:

Even if you have a client-server setup, CRDTs are still worth researching because they provide a well-studied, solid foundation to start with. Understanding them helps build intuition on how to create a correct system. From that point, it's possible to relax some of the requirements of CRDTs based on the needs of the application as we have done.

Os trade-offs do desenvolvimento local-first

Como qualquer decisão arquitetural, local-first vem com seus próprios desafios. Vamos analisar os principais trade-offs e como eles afetam diferentes tipos de aplicações.

Sincronização e Consistência

Em uma arquitetura tradicional, o servidor é a fonte da verdade. Em local-first, cada cliente tem sua própria cópia dos dados.

Prós:

  • Operações instantâneas para o usuário
  • Trabalho offline possível
  • Sem single point of failure

Contras:

  • Estado pode ficar temporariamente inconsistente
  • Resolução de conflitos é complexa (CRDTs ajudam com isso)
  • Usuários podem ver versões diferentes do mesmo dado

Em aplicações local-first, você faz os updates localmente, que eventualmente são sincronizados com outros clientes. Fonte: Palestra do Anselm Eickhoff, criador jazz.toolsno meetup Local-first Berlin. Em aplicações local-first, você faz os updates localmente, que eventualmente são sincronizados com outros clientes.

Performance e Recursos

Prós:

  • Queries são rápidas (dados locais)
  • Menor carga nos servidores
  • Zero latência para operações básicas

Contras:

  • Dados ocupam espaço no dispositivo
  • CRDTs podem crescer muito com histórico
  • Sincronização inicial pode ser lenta

Por exemplo, o Figma precisa baixar o arquivo de design completo antes que você possa começar a editar. Em arquivos grandes, isso pode levar alguns segundos.

Segurança e Validações

Este é provavelmente o trade-off mais significativo. Em uma arquitetura tradicional, podemos confiar que o servidor vai validar todas as operações.

Prós:

  • Dados sensíveis ficam no dispositivo
  • Menor superfície de ataque
  • Usuário controla seus dados

Contras:

  • Validações precisam ser duplicadas
  • Difícil revogar acesso a dados
  • Vazamentos são mais difíceis de detectar

Uma estratégia comum é ter validações em camadas:

  1. Validação local para feedback rápido
  2. Validação no servidor antes de propagar mudanças
  3. Reconciliação periódica de dados

Complexidade de Desenvolvimento

Prós:

  • Menos infraestrutura para manter
  • Escala naturalmente
  • Menos código de API

Contras:

  • Debugging mais complexo
  • CRDTs tem curva de aprendizado
  • Ferramental ainda imaturo

James Arthur, fundador do ElectricSQL, compartilhou em uma palestra que um dos maiores desafios em sistemas local-first é rastrear a origem de bugs. Como os dados podem vir de múltiplos dispositivos e serem modificados offline, pode ser difícil entender como o sistema chegou em determinado estado.

Um exemplo comum é quando diferentes clientes têm versões diferentes do schema do banco. Enquanto em sistemas tradicionais isso seria detectado imediatamente (pois há apenas um schema no servidor), em local-first isso pode causar inconsistências sutis que são difíceis de diagnosticar.

Como essa nova versão? Ela mantém o ponto sobre a complexidade de debugging mas usa um exemplo real e citado, ao invés de inventar uma experiência.

Estratégias para mitigar problemas comuns

  1. Sincronização Seletiva

Em vez de sincronizar todos os dados, permita que o usuário escolha o que manter local:

Fazendo sincronização seletiva com o ElectricSQL.

  1. Compressão de História

CRDTs podem crescer indefinidamente. Uma solução é comprimir o histórico periodicamente, mantendo apenas as mudanças mais recentes.

  1. Cache Inteligente

Use estratégias como LRU (Least Recently Used) para manter apenas dados relevantes no cliente:

Quando faz sentido usar local-first

Local-first não é uma bala de prata. Existem casos onde ele faz muito sentido, e outros onde uma arquitetura tradicional pode ser melhor.

Bons casos para local-first:

  • Editores de texto/código
  • Ferramentas de design
  • Apps de notas e documentação
  • Jogos e aplicações multiplayer
  • Qualquer app que precise funcionar offline

Além disso, um outro bom caso: onde a qualidade do seu produto é essencial para captar clientes.

Foi assim que o Linear cresceu como uma ferramenta de gerenciamento de projetos.

Esse é um espaço que é competitivo. Muitas empresas competem aqui: Asana, Atlassian (Jira/Trello), até mesmo GitHub/GitLab.

Porém, um dos motivos que fez o Linear conseguiu adentrar esse mercado é devido a qualidade do seu sistema.

Aplicações local-first, com suas interações imediatas e rápidas, podem conquistar usuários através da força de sua UX.

Casos onde local-first pode não ser ideal:

  • Redes sociais
  • E-commerce
  • Bancos
  • Apps que dependem muito de dados em tempo real do servidor

A regra geral é: se sua aplicação precisa funcionar offline ou ter colaboração em tempo real, local-first pode ser uma boa escolha.

Ferramentas disponíveis

O ecossistema está crescendo rapidamente:

  • ElectricSQL: Sincronização Postgres ↔ SQLite
  • Yjs: Framework para dados compartilhados
  • PouchDB: Banco local que sincroniza com CouchDB
  • Automerge: Estruturas CRDT em JavaScript
  • Replicache: Biblioteca de sincronização para qualquer backend
  • PowerSync: Sincronização com first-party support para o Supabase (Postgres)

Veja mais ferramentas aqui no localfirstweb.dev.

Se quiser saber mais sobre as atualidades desse cenário, também recomendo ver as palestras da Local-First Conf.

🌟 Resumo

  • Local-first é uma mudança de paradigma onde dados ficam primariamente no cliente
  • Os benefícios principais são performance, suporte offline e colaboração
  • CRDTs permitem sincronização sem conflitos mas tem seus próprios desafios
  • Trade-offs significativos em segurança e consistência precisam ser considerados
  • O ecossistema está amadurecendo rapidamente

A chave é entender que local-first não é um substituto completo para arquiteturas tradicionais - é uma ferramenta diferente com seus próprios casos de uso.

Se você está construindo uma nova aplicação que precisa de colaboração em tempo real ou suporte offline robusto, considere uma arquitetura local-first. As ferramentas estão ficando maduras e os benefícios podem superar os desafios.

O futuro da web pode ser mais descentralizado do que imaginamos. E desenvolvimento local-first é um passo importante nessa direção.


Esse artigo foi publicado na minha newsletter, Dev na Gringa.

Se você gosta do meu conteúdo, considere se inscrever para receber diretamente por e-mail.

Enquanto gostei do artigo, ele faz o local-first parecer algo de outro mundo… quando, na verdade, é bem mais simples do que isso!

Então aqui vai um contra ponto, todas essas ferramentas modernas, mas não existe nada melhor que o bom e velho Git para CRDTs. Com o git e sqlite dá para fazer tudo, e muito do que essas ferramentas prometem.

Não sei se esse é o futuro da web. Parece uma maneira bem complicada de fazer uma aplicação desktop? Mas falando sério, já é um grande passo não fazer seus dados darem voltas ao globo várias vezes para renderizar a tela do cliente ;)

Um abraço e bons estudos!

Importante ressaltar que o Replicache que você mencionou logo mais vai ser descontinuado pra dar espaço pro ZeroSync.

Excelente artigo, abriu muito minha mente. Posso estar errado, mas fiquei com a impressão no final de que os contras superam os prós dependendo do ponto de vista.

Toda arquitetura é assim 😅 Eu acho que pra apps de produtividade e colaboração, os benefícios de local-first são muito difíceis de bater. E isso inclui muitos, por exemplo: - Qualquer tipo de planilha - Redes sociais nichadas (como o TabNews). Apesar disso, o BlueSky por exemplo implementou com sucesso uma arquitetura local-first, onde todos os seus dados ficam em um banco de dados local - Apps que você usa pra trabalhar/organizar coisas (Linear, Figma, Observabilidade/Monitoramento, Gmail, Notion)

Tão lindo na teoria, na prática, bem díficil de aplicar, sincronização de dados locais com servidor entre multiplos clientes é MUITO complexo.

Já existem alguns apps com usuários que tem esse protocolo implementado: - [Linear](https://linear.app/) - [Figma](https://figma.com/) - [Excalidraw](https://excalidraw.com/) É sim, complexo, mas nem todas as aplicações precisam de algo assim. Os ganhos de UX e simplificação do código fazem essa mudança valer a pena eu diria. O uso das CRDTs ajuda com a parte de sincronização e entender quem fez o que. Mas certamente não é ideal pra todo tipo de aplicação. Mas, para apps de produtividade, acho difícil de bater essa arquitetura.
Não vejo como simplificação de código a necessidade de manter lógica de negócio, lógica de armazenamento e de banco de dados em vários lugares. São poucos casos que exigem isso. Mas não concordo com a parte de simplificação de código. Nos meus sistemas eu mantenho os dados em memória, para carregar somente uma vez e a cada mudança eu salvo localmente quando recebo o retorno da requisição feita. Só isso. E só isso é muuuuito mais complexo do que requisitar os dados a cada vez que entrar na tela. Muito. E eu sequer armazeno localmente, sequer tenho estrutura de sincronização e sequer tenho resolução de conflitos, nem filas etc. Enfim... acho que é válido, sim. Meu sistema foi escrito com esse propósito futuro, mas não é nada simples.

Muito interessante, acho que pra algumas coisas fica muito bom.

Pensei no caso de aplicações como bloco de notas, listas de compras, sites médicos talvez.

Acho que o notion seria uma boa fazer isso. Deixar as paginas privadas dele local e as compartilhadas só na nuvem.

Outro caso que poderia ser util é colocar algumas informacoes de sites de videos. YouTube deixar playlists e historicos localmente talvez seja uma boa 🤔

Disruptivo. Obrigado por alargar nossos horizontes. Passei a ver de outra forma a construção de aplicações web.

Gostei bastante, uma coisa que observei é que ele usa o eletric que por sua vez, utiliza PGLite, o problema é que o PGlite é em wasm e não tem total compatibilidade ou estou errado?

Enfim, falei sobre isso pra falar que a poucos dias busquei por uma alternativa local de base de dados relacional no browser, e encontrei o PGlite e Sql.js, que é um SQlite em javascript com duas versões uma em wasm e outra em js puro (o que pode deixar mais lento, porém o quão lento não é mesmo).

Enfim... Posso estar errado sobre a compatibilidade.

Em resumo, quase nada útil podera ser local First.

Para você ter tirado uma conclusão tão precipitada com certeza não deves ter lido o texto na íntegra. Diversos aplicativos dependem da feature "local first", afirmar que "quase nada útil" poderá ser feito no local first mostra a sua falta de dominância no assunto.