Linear me llevó al agujero de conejo del enfoque local-first
(bytemash.net)- Linear cambió por completo mi forma de ver el desarrollo de aplicaciones web
- Linear opera en modo local-first, ofreciendo interacciones instantáneas y sin latencia de red
- En este modelo, el cliente tiene una base de datos independiente y los cambios se sincronizan con el servidor de forma asíncrona
- Sin embargo, la implementación de sincronización en entornos distribuidos, resolución de conflictos y procesamiento offline tiene alta complejidad
- Surgen soluciones cada vez más diversas en el ecosistema local-first como Jazz, Electric SQL, Zero, y la experiencia para desarrolladores también mejora poco a poco
Resumen
Al usar Linear como herramienta de gestión de proyectos, me sentí muy inspirado por la velocidad y la experiencia de usuario del enfoque local-first. Lo más llamativo fue que en una app web tradicional casi no se percibían latencia de red, estados de carga o recargas de página. A partir de esta experiencia, exploré a fondo los principios técnicos y los casos reales del paradigma local-first.
Bajando a la madriguera de conejo
Al investigar los secretos técnicos de Linear descubrí que usan el IndexedDB del navegador como una base de datos real. Todos los cambios se procesan primero de inmediato en local, y luego se sincronizan en segundo plano mediante GraphQL y WebSockets.
- El término local-first puede interpretarse como una estrategia de UX (respuesta instantánea) o como una filosofía de guardar los datos localmente y sincronizarlos
- En las apps web tradicionales, el servidor era la fuente única de verdad, pero en una arquitectura local-first cada cliente tiene su propia base de datos
- Al mover la ubicación de la base de datos cerca del usuario, la latencia de red se elimina por completo de la interacción del usuario
El reto: esto no es trivial
Al intentar implementar directamente el enfoque de Linear, me di cuenta de que la complejidad es considerable.
- Manejo de transición offline/online
- Resolución de conflictos entre clientes distribuidos
- Sincronización parcial (para no descargar todos los datos)
- Migración de esquema de datos en caché
- Seguridad y control de acceso en entornos distribuidos
- Estas capacidades requieren una gran cantidad de tiempo y esfuerzo de ingeniería
El ecosistema local-first en 2025
A partir de 2025, en el ecosistema local-first han aparecido varias soluciones fuertes.
- Electric SQL: motor de sincronización basado en Postgres
- PowerSync: solución con foco empresarial
- Jazz: herramienta para construir apps local-first con facilidad
- Replicache: solución principal anterior (descontinuada)
- Zero: nueva dirección del equipo de Replicache
- Triplit: sincronización basada en TripleStore
- Instant: con énfasis en la experiencia del desarrollador
- LiveStore: proporciona una capa de sincronización en tiempo real
Análisis en profundidad: Jazz
Jazz llama la atención con su promesa distintiva de que “hacer una app local-first es tan fácil como actualizar estado”.
El modelo mental
Jazz introduce los Collaborative Values (CoValues), una estructura de colaboración en tiempo real.
- Definir esquema con Jazz y Zod: esa definición no es solo un tipo simple, sino un objeto vivo de sincronización automática
- Sin rutas API separadas, lógica de request/response ni DTO, basta con cambiar el estado del objeto para que la sincronización ocurra automáticamente
Cómo logra esto Jazz
La estructura interna de Jazz es la siguiente:
- Unicidad garantizada: asigna un ID único automáticamente a cada dato
- Event sourcing: guarda el historial de cambios como eventos, optimizando la eficiencia de la sincronización en tiempo real
- Cifrado de extremo a extremo: los datos se cifran en el cliente antes de sincronizar. El servidor solo ve blobs cifrados
- Permisos basados en grupos: en vez de ACL tradicionales, permisos por grupo, con propiedad claramente separada
Las compensaciones
Esta estructura es muy productiva para prototipado y desarrollo de UI rápido. Pero por ciertas características hay que considerar algunos puntos:
Tu servidor está ciego
Con el cifrado de extremo a extremo, el servidor no puede leer los datos del usuario. Si no defines con antelación qué datos debe acceder el servidor, hay limitaciones para gestión como monitoreo o prevención de almacenamiento malicioso
El viaje en el tiempo es obligatorio
Gracias al event sourcing, todo el historial de cambios se guarda de forma permanente. Esto facilita muchísimo Undo/Redo, pero tiene el inconveniente de que, al considerar requisitos legales como GDPR, el borrado puede ser difícil
El almacenamiento se dispara
Al no ejecutarse eliminaciones, el uso de almacenamiento crece gradualmente. En proyectos pequeños está bien, pero en SaaS a gran escala el costo de almacenamiento puede aumentar considerablemente
El desarrollo local aún tiene peculiaridades
La autenticación basada en Passkeys es predeterminada, pero en desarrollo propio o local hay molestias iniciales con HTTPS, administración de certificados y migración de llaves. Sin embargo, se prevén mejoras como la integración con Better Auth
Pero honestamente, aún vale la pena
Aun con esas restricciones, la experiencia de desarrollo y productividad de Jazz es muy llamativa. Aún está en versión temprana, pero se espera que diversos problemas se vayan resolviendo con el tiempo.
Explorando: Electric SQL y Zero
A diferencia de Jazz, Electric SQL y Zero adoptan un enfoque incremental.
- Puedes seguir usando tus tablas de Postgres tal como están
- Electric SQL permite suscribirse por reactive query (Shape) a una parte de la tabla para sincronizarla con la UI
- El manejo de mutaciones difiere de Jazz, y hay diversas opciones, como integración con LiveStore
- Zero es parecido a Electric pero incluye soporte integrado para sincronización de cambios
¿Cuándo tiene sentido el local-first?
Se resume así cuándo el paradigma local-first es apropiado y cuándo es desafiante
Apropiado:
- Herramientas de creación (diseño, escritura, música, etc.)
- Aplicaciones con soporte de colaboración
- Apps móviles con necesidad de soporte offline
- Herramientas para desarrolladores
- Aplicaciones de productividad personal
Desafiante:
- Lógica de negocio pesada del lado del servidor
- Requisitos estrictos de auditoría
- Sistemas de analítica de gran volumen
- Sistemas profundamente integrados existentes
- Sistemas que rechazan solicitudes con frecuencia desde el servidor
Mirando hacia adelante
El local-first significa un cambio de paradigma en el desarrollo web. Linear ya demostró un gran impacto en la experiencia de usuario. El desarrollador debe valorar si estas compensaciones estructurales encajan en su proyecto.
Con Jazz estoy construyendo una app personal para experimentar directamente sus ventajas, desventajas reales y los límites de la abstracción. El ecosistema aún está en una etapa inicial y, con el tiempo, las herramientas y patrones madurarán y mejorarán. Sin embargo, el beneficio de tener los datos en local es claro y parece que no va a desaparecer.
Si un nuevo proyecto puede aceptar las limitaciones, vale la pena probar el local-first. En el peor caso, aprenderás un nuevo patrón de arquitectura; en el mejor, podrás implementar una experiencia de usuario increíblemente rápida. En la competencia de respuesta en 300 ms, esto se vuelve una ventaja clave.
1 comentarios
Opinión en Hacker News
Zero también ofrece funcionalidades similares a Electric y soporta la actualización de estado (mutaciones) de forma directa. La principal diferencia de Zero es la sincronización centrada en consultas. Puedes diseñar la app por unidad de consulta y, sin definir antes o configurar qué sincronizar, al ejecutar la consulta se sincroniza solo la cantidad necesaria (o menos). Si en el cliente no existen datos necesarios, la consulta se envía automáticamente al servidor y esos datos quedan sincronizados para usarse de inmediato en consultas posteriores. Este enfoque es bastante práctico.
Prefiero SSR. Creo que el cliente debería guardar como estado solo un token de sesión, la URL actual y el DOM. La red y los servidores siguen mejorando. La velocidad de la luz es fija, pero todavía no estamos en el punto de aprovecharla al máximo. También están llegando tecnologías como el hollow core fiber que pueden reducir la latencia de internet global hasta un 30%. Incluso con un RTT de 500 ms, si SSR entrega una página renderizada en 16 ms, se sentirá infinitamente más instantánea que la mayoría de sitios web actuales. Para mí no tiene sentido que el servidor tarde más de 60Hz en renderizar una respuesta HTML. Con un núcleo Zen5 se puede serializar de 30 a 40 MB de JSON en ese tiempo. Del lado del servidor, es simplemente una cadena UTF-8 elegante. Ese tipo de cosas hay que medirlas en microsegundos (μs), no en milisegundos (ms). Que la latencia de transmisión sea alta no justifica gastar la CPU de forma perezosa. Yo salí de la "cárcel de milisegundos" usando SQLite. Los servicios SQL hospedados se vuelven una atadura cuando se busca latencia por debajo de 1 ms. Los estándares del navegador también pueden resolver parte de la latencia de navegación. Speculation Rules API
El hecho de que la red y los servidores mejoren no justifica de manera universal SSR. Es más, significaría que hay casos puntuales que requieren SSR: cuando hay más poder de cómputo en un servidor específico o cuando es difícil separar lógica. En la práctica, muchas veces el renderizado en cliente es más rápido. Muchas veces la lógica de renderizado es mucho más compleja en relación con el tamaño de los datos y, al sumar recursos de todos los clientes, puede superar al servidor. Si SSR hubiese sido la única opción, no se explicaría el entusiasmo actual por Web Assembly. También vale la pena leer el artículo sobre local computation. En última instancia, no se puede saber de antemano qué es mejor, y debería poder elegirse en el momento de la solicitud.
Right Tool For The Right Job! ¿Podemos trabajar en documentos colaborativos tipo Google Docs o Sheets solo con renderizado del lado del servidor? Si uno se aferra a SSR únicamente, termina en una situación donde otra persona puede cambiar el contenido mientras estás editando a mitad. Este tipo de herramientas funciona bien porque el impacto de los cambios es pequeño y, al colaborar, los usuarios perciben al instante lo que pasa y reciben actualizaciones en tiempo real. En cambio, servicios como reservación de hoteles no necesitan sincronización en tiempo real. Además, en una app enterprise con sync inestable o con diseño insuficiente, luego por la complejidad de dominio y sistema la consistencia suele ser difícil de ajustar.
Si fuera fácil agregar a una app SSR una librería como esta, la usaría:
Esto es ingeniería del camino feliz. La realidad puede ser bastante frustrante para quienes no están en ese camino feliz.
Los casos de uso principales de SSR, hoy y de cara al futuro, son la primera carga de página, especialmente en móvil. Después de eso, con actualizaciones de estado en cliente, todo debe ser más rápido. Si no hay habilidad de ingeniería, tener SSR no significa mucho.
Desarrollé un software de gestión de tareas de código abierto con arquitectura local-first basada en CRDT. La motivación fue que no necesito funcionalidad de colaboración y que herramientas como Linear eran demasiado complejas para mis propósitos. Las ventajas de esta arquitectura son:
list/save/read, usando por plataforma IndexedDB (navegador), filesystem (Electron) y Capacitor Filesystem (iOS/Android).Me dejó una impresión enorme Jazz. El DX es excelente y casi se siente como programación imperativa y síncrona, así que desarrollar se vuelve divertido. Todo se siente inmediato y también es posible trabajar offline, por lo que la UX es excelente. El mayor problema es el despliegue y la mantenibilidad a largo plazo, además de que los datos siguen creciendo (en general no importa tanto porque la mayoría de clientes no necesita ver todo). El mayor problema es que no hay una buena solución para un
public indexque cambia seguido. En teoría, una lista pública de IDs legibles es posible, y hace poco hablé con Anselm y me dijo que están en eso. En general, local-first también tiene su costo, pero creo que si Jazz resolviera las principales debilidades del enfoque de servidor tradicional, podría reemplazar a Firestore en casi todo.ElectricSQL y TanStack DB están excelentes, pero me pregunto por qué tanto enfoque en local-first en web. En mobile la necesidad es mucho mayor por escenarios offline. En el navegador, en la mayoría de los casos, internet ya está conectado. Además, estas tecnologías local-first suenan bien en teoría, pero si la resolución de conflictos no las acompaña, se rompen fácilmente. Eso fue una lección al intentar hacer mobile apps local-first, y por eso terminé usando CRDT.
Hacer apps local-first con web tech es infinitamente más difícil que en nativo. En nativo, con solo instalar la app ya tienes soporte offline por defecto. En web, tienes que “agregar” eso de forma forzada con AppManifest, ServiceWorker y trucos similares. En nativo, 30 años de código en C para leer/escribir en disco resuelven esto. En web, IndexedDB es una pesadilla, localStorage no sirve cuando crece la escala, y OriginPrivateFileSystem también es limitado. Si un usuario no visita el sitio ni una vez al mes, Safari elimina el estado local del navegador. Aunque pruebes cosas raras como construir wasm con JavaScript o Emscripten, al final terminas sorteando las API web asíncronas de cualquier modo. Apple, desde 2015, ya ofrece una solución casi completa de local+sync con CoreData + CloudKit. Del lado de Google, Firebase me parece lo más parecido.
Como desarrollador de Replicache y Zero, he pensado mucho en por qué se enfocan en web. No tengo la respuesta, pero comparto mi opinión:
Mobile tiene capacidades offline de plataforma más fuertes que web. Pero la mayor parte de productividad/colaboración ocurre en web, y ese entorno es más hostil. Hay problemas complejos de sincronización de estado entre pestañas, borrado de almacenamiento, etc. Por eso local-first se está desarrollando sobre todo en torno a web.
Los motores de sincronización apuntan primero a web porque esta tecnología aún es joven y hay muchas actualizaciones. Actualizar apps móviles es realmente doloroso, pero actualizar una webapp es mucho más fácil. También creo que el stack de PWA ya está bastante decente. iOS PWA también permite notificaciones push, y yo estoy satisfecho con una webapp que se sienta como app móvil.
El punto aquí no es “qué función se puede usar”, sino la “diversión” que se siente con un producto extremadamente rápido y reactivo. Por eso apuesto por local-first.
Siento que no se discute mucho la parte de colaboración en motores local-first/sync. Estoy usando Zero para construir un proyecto que reemplaza Google Sheet para el negocio de un amigo. Estamos en Google Meet con un Sheet abierto para operar datos en tiempo real. Esa experiencia antes no la habría considerado posible de construir como webapp. He hecho muchas UIs en vivo con websockets, pero ubicar y gestionar correctamente los datos entrantes en el lugar correcto es extremadamente complejo. Si hay cientos o miles de celdas, el caos es inimaginable. Con Zero, al seleccionar datos por consulta y mutar con mutator, todo se sincroniza para todos de forma inmediata. Gracias a eso, desarrollar en sí mismo es realmente divertido.
Cuando salió Google Docs, tenía 12 años. Al ver que traía sincronización en tiempo real y cursores colaborativos, pensé: “la experiencia web del futuro sería totalmente colaborativa”. En ese momento, cloud computing era una palabra de moda y creía, erróneamente, que la colaboración en tiempo real definía la computación en la nube. Pero ese futuro no terminó materializándose. Han pasado 20 años y la mayoría de productos web siguen siendo experiencias CRUD (este sitio también). Cada vez creí que la innovación estaba a la vuelta; esperé cosas de Meteor.js, React y demás, y aun así no son el default. Sigo esperando que esto se vuelva mainstream; finalmente creo que se volverá realidad. Solo que es más difícil de lo esperado y no hay tooling lo suficientemente simple para todos los casos.
Como ejemplos de colaboración en tiempo real están Discord (esencialmente no demasiado distinto del IRC de los 90) y Zoom (todas las videollamadas). Que HN sea una app CRUD es una ventaja. La actualización en tiempo real puede resultar disruptiva y a veces ni se desea.
La velocidad de la luz sí impone un límite. La luz tarda 56 ms en media vuelta al planeta, la señal recorre rutas más largas y además hay conversiones y capas adicionales (load balancer, protección DDoS, etc.) que la vuelven más lenta. La latencia muchas veces también es peor hoy que antes.
Yo también sentí esa magia inicial de la inmediatez, pero esa promesa parece seguir ahí, en algún lugar. Aun así, sigo construyendo una herramienta real de mensajería al estilo Google Docs sin caja de entrada ni botón de enviar: kraa.io/hackernews
Creo en el futuro de local-first y sync engines. La comparación de tablas del ecosistema de local-first es realmente valiosa. Personalmente, Triplit.dev me pareció lo mejor, y puede combinarse también con TanStack DB. PowerSync y NextGraph también son opciones que quiero explorar. También he visto con interés una charla reciente sobre NextGraph en la LocalFirst Conf: video de YouTube
Me pregunto qué tan buen soporte de migraciones tienen estas herramientas. Cuando hay que soportar clientes sin acceso al servidor por mucho tiempo, la migración de roll-forward desde un esquema extremadamente viejo puede ser una traba enorme. Si depurar errores de frontend de usuario único ya es difícil, con estado de esquema incluido, la complejidad parece ser inimaginable.
Recuerda mucho a Meteor.
También estos enfoques son en parte maneras del pasado.
Tengo que probar Triplit. Me gustaría saber si alguien ya usó InstantDB. Yo tenía curiosidad pero aún no la he podido probar.
Me ha gustado mucho la aproximación de Electric y por eso la sigo de cerca desde hace tiempo. Electric, al dejar la complejidad de escritura al desarrollador y al API, se siente bastante limpio. Las soluciones de sincronización bidireccional suelen funcionar bien únicamente a nivel de apps tipo Todo. Cuando agregas permisos, evolución de lógica de negocio, migraciones y crecimiento de producto, la durabilidad se cuestiona. Electric soporta solo sincronización de lectura por view y mantiene escritura en API/rest/rpc, por lo que es más fácil adoptarlo en proyectos existentes.
Me extraña que CouchDB / PouchDB no estén en la lista. Aún sigue siendo una herramienta bastante funcional.