Bastan unos pocos milisegundos para actualizar un issue en Linear. La misma operación en una aplicación CRUD tradicional ronda los 300 ms, ese tiempo suficiente para que aparezca un spinner o un esqueleto de carga mientras la interfaz espera a la red. La diferencia no nace de un único truco, sino de una decisión arquitectónica tomada desde el primer día: una arquitectura local-first.
En este artículo desmenuzamos cómo Linear consigue esa sensación de velocidad casi nativa, qué piezas la hacen posible y, sobre todo, cómo aplicar las mismas ideas en proyectos reales sin necesidad de reinventar todo el motor de sincronización.
TL;DR
- Linear actualiza un issue en pocos milisegundos; un CRUD tradicional con ida y vuelta al servidor tarda alrededor de 300 ms.
- La base de datos que lee la UI vive en el navegador, dentro de IndexedDB, no en el servidor remoto.
- Las mutaciones se aplican primero en local (observable MobX) y luego se sincronizan en segundo plano vía WebSocket.
- Tuomas, cofundador de Linear, escribió el sync engine como las primeras líneas de código del producto en 2019.
- El stack es deliberadamente simple: React, TypeScript, MobX, PostgreSQL y un CDN, sin edge database ni RSC.
- Sin sync engine propio, librerías como TanStack Query o SWR logran el 80% del efecto con updates optimistas.
- La regla central: la respuesta de la UI no debe depender de la latencia de red.
Qué hace a Linear tan rápido
La mayoría de las aplicaciones web viven dentro del mismo bucle: el usuario hace clic, el navegador dispara una petición HTTP, un servidor consulta una base de datos y devuelve la respuesta, y recién entonces el navegador vuelve a pintar la pantalla. El resultado es una interfaz congelada durante unos cientos de milisegundos cada vez que ocurre algo. Multiplicá eso por las decenas de pequeñas acciones que hace una persona en una herramienta de trabajo y entendés por qué tantas apps se sienten lentas.
Linear invierte esa relación tradicional. En lugar de tratar al servidor como la fuente de verdad inmediata, trata al navegador como la base de datos del usuario. La interfaz lee de un almacén local, las mutaciones se aplican primero ahí, y la sincronización con el servidor sucede de forma asíncrona, fuera del camino crítico que percibe el usuario. Esta es la idea que sostiene toda la experiencia, y es el núcleo de cualquier arquitectura local-first bien hecha.
💭 Clave: el usuario percibe la velocidad por la rapidez con la que reacciona la interfaz, no por la rapidez con la que responde el servidor. Si separás ambas cosas, ganás la batalla.
La base de datos vive en el navegador
La pieza más crítica del rendimiento de Linear es que la base de datos que lee la UI está dentro del navegador, en IndexedDB. Cuando la app arranca, hidrata un grafo de objetos observables en memoria (Linear usa MobX) a partir de ese almacén local. A partir de ahí, leer y escribir datos no implica una petición de red: implica tocar memoria.
Pensalo así: el cuello de botella número uno al construir una app web rápida es la red. Cualquier dato que viaje entre cliente y servidor cuesta cientos de milisegundos, y desde El Salvador, México o Argentina, hablando con un servidor en us-east, esa latencia es todavía mayor que en la costa oeste de Estados Unidos. La mejor optimización no es hacer la petición más rápida: es eliminar la necesidad de hacerla. El secreto para construir apps web excepcionales es esconder todas las peticiones de red al usuario y reducir al mínimo los estados de carga.
Mirá lo simple que se ven las escrituras cuando la base de datos vive en el cliente:
// App tradicional: la UI espera al servidor
async function actualizarIssue(issue) {
mostrarSpinner();
const res = await fetch(`/api/issues/${issue.id}`, {
method: 'PATCH',
body: JSON.stringify({ titulo: issue.titulo }),
});
const actualizado = await res.json();
setIssue(actualizado);
ocultarSpinner();
}
// Linear: el estado vive en el cliente
issue.titulo = 'Lanzamiento más rápido';
issue.save();
La primera línea del ejemplo de Linear actualiza un almacén en memoria (un observable de MobX). La segunda encola una transacción que el sync engine agrupa y envía al servidor en lotes. Lo importante es que la UI se re-renderiza de forma síncrona a partir de ese cambio local: no hay spinner porque no hay nada que esperar. Esa es la magia de tratar al navegador como la base de datos de cada usuario.
La UI lee de un almacén en memoria hidratado desde IndexedDB.
El sync engine: el corazón de todo
Que las mutaciones sean instantáneas en local es solo la mitad de la historia. La otra mitad es mantener esos datos consistentes entre todos los dispositivos y usuarios sin que nadie vea un spinner. De eso se encarga el sync engine: un motor que toma las transacciones encoladas, las envía al servidor en lotes, persiste en PostgreSQL y luego propaga los cambios (deltas) al resto de los clientes conectados mediante WebSocket.
Tuomas, uno de los cofundadores de Linear, lo resumió en una charla en 2024: "Literalmente las primeras líneas de código que escribí fueron el sync engine, algo muy poco común cuando estás en una startup". Desde el día uno el equipo sabía el enfoque que quería y las contrapartidas que implicaba. Construir un motor de sincronización antes que cualquier feature es una apuesta arquitectónica enorme, pero es la que define el carácter del producto.
Este es, a grandes rasgos, el viaje de una mutación dentro de la arquitectura local-first:
graph LR
A["Usuario edita un issue"] --> B["Store en memoria (MobX)"]
B --> C["UI re-renderiza al instante"]
B --> D["IndexedDB (cache local)"]
D --> E["Sync engine: cola de transacciones"]
E --> F["Servidor + PostgreSQL"]
F --> G["WebSocket: deltas a otros clientes"]
Fijate que las ramas C (renderizado) y E (sincronización) salen del mismo punto, pero la primera es síncrona y la segunda es asíncrona. El usuario solo experimenta la rama rápida; toda la coordinación con el servidor ocurre en segundo plano y, si algo falla, el sistema hace rollback del cambio local.
Updates optimistas: el 80% del beneficio sin un sync engine propio
La buena noticia para el resto de nosotros es que casi nadie necesita construir un sync engine a medida como el de Linear. Para la mayoría de los casos de uso, librerías como TanStack Query o SWR se acercan sorprendentemente al mismo efecto usando updates optimistas. La idea es simple: como la inmensa mayoría de las peticiones de escritura van a tener éxito, aprovechá esa probabilidad y actualizá el estado de inmediato, validando contra el servidor después.
// Update optimista con SWR
mutate(
`/api/issues/${issue.id}`,
{ ...issue, titulo: 'Lanzamiento más rápido' },
false // no revalidar de inmediato
);
// Equivalente conceptual en Linear
issue.titulo = 'Lanzamiento más rápido';
issue.save();
Las mutaciones optimistas son una de las mejoras de mayor impacto que podés hacer en una app web. En resumen, te permiten: eliminar spinners innecesarios, actualizar el estado al instante, validar en segundo plano y hacer rollback únicamente cuando algo sale mal. No es la arquitectura completa de Linear, pero captura la idea central que la hace sentir nativa y veloz.
⚠️ Ojo: el update optimista necesita una estrategia de rollback clara. Si el servidor rechaza la mutación y no revertís el estado local, el usuario verá datos que en realidad no se guardaron. Definí siempre el camino de error antes de salir a producción.
Update optimista: la interfaz reacciona antes de que responda el servidor.
El stack real de Linear
Una de las lecciones más valiosas es que Linear está construido sobre uno de los stacks más simples que vas a encontrar: React, TypeScript, MobX, PostgreSQL y un CDN. No hay base de datos en el edge, no hay React Server Components, no hay un framework exótico de moda. La sofisticación está en la arquitectura local-first y en el sync engine, no en acumular tecnología.
- Frontend — React y react-dom para la UI, MobX para el grafo de observables y los re-renders granulares, TypeScript de punta a punta, ProseMirror con Yjs (CRDT) para edición colaborativa en tiempo real, y un wrapper de IndexedDB (idb) que respalda el store local.
- Backend — Node.js con TypeScript, PostgreSQL en Cloud SQL con la tabla de issues particionada en 300 fragmentos, Redis (Memorystore) como bus de eventos y caché, y Cloudflare Workers como proxy multi-región en el edge.
- Otros clientes — Desktop con Electron reutilizando el mismo JavaScript web; móvil reescrito de forma nativa en Swift (iOS) y Kotlin (Android).
📌 Nota: que el stack sea "aburrido" es una característica, no un defecto. Tecnologías maduras y bien entendidas dejan toda la energía del equipo disponible para lo que de verdad diferencia al producto: la sincronización.
Cómo aplicarlo en tus proyectos en LATAM
Si desarrollás desde Latinoamérica, la latencia juega todavía más en tu contra que para un equipo en San Francisco. Una petición que viaja desde un navegador en Bogotá o Buenos Aires hasta un servidor en Virginia y vuelve puede sumar fácilmente 150 a 250 ms solo de ida y vuelta de red, antes de contar el tiempo de procesamiento. Cada estado de carga que elimines vale el doble para tu audiencia regional.
No hace falta replicar el sync engine completo de Linear para mejorar de forma drástica. Una hoja de ruta razonable, de menor a mayor esfuerzo, sería: primero, adoptar updates optimistas con TanStack Query o SWR en las acciones más frecuentes (marcar como completado, renombrar, mover entre estados). Segundo, cachear en local los datos que el usuario consulta una y otra vez, idealmente en IndexedDB para que sobrevivan a recargas. Tercero, si tu producto lo justifica, evaluar soluciones local-first ya construidas como Replicache, ElectricSQL o el propio enfoque CRDT con Yjs, en lugar de escribir el motor desde cero.
💡 Tip: empezá midiendo. Activá el log de peticiones en desarrollo y contá cuántas escrituras bloquean la UI con un spinner. Convertí primero las tres más frecuentes a updates optimistas: ahí está el mayor retorno con el menor riesgo.
El principio que une todo esto es el mismo que repite el equipo de Linear una y otra vez: la capacidad de respuesta de la interfaz no debería depender de la latencia de red. Una vez que internalizás esa regla, dejás de preguntarte "cómo hago esta petición más rápida" y empezás a preguntarte "cómo evito tener que hacer esta petición ahora". Ese cambio de mentalidad, más que cualquier librería, es lo que separa a las apps que se sienten instantáneas de las que se sienten pesadas.
📖 Resumen en Telegram: Ver resumen
Preguntas frecuentes
¿Qué significa exactamente arquitectura local-first?
Significa que la fuente de verdad para la interfaz vive en el dispositivo del usuario, no en el servidor remoto. Las lecturas y escrituras se hacen contra un almacén local (memoria más IndexedDB) y la sincronización con el servidor ocurre en segundo plano. El servidor sigue siendo la fuente de verdad última y el punto de coordinación, pero ya no está en el camino crítico de cada interacción.
¿Necesito construir un sync engine como el de Linear?
Casi nunca. Para la mayoría de las aplicaciones, los updates optimistas con TanStack Query o SWR capturan gran parte del beneficio percibido. Un sync engine propio solo se justifica cuando la colaboración en tiempo real y el trabajo offline son requisitos centrales del producto.
¿Por qué Linear usa MobX y no Redux o un store más popular?
MobX ofrece reactividad granular: cuando cambia un campo de un objeto observable, solo se re-renderizan los componentes que dependen de ese campo. Para un grafo de datos grande que se actualiza constantemente vía sincronización, esa granularidad reduce el trabajo de renderizado y mantiene la UI fluida.
¿Qué pasa si una mutación local falla en el servidor?
El patrón optimista contempla el rollback: si el servidor rechaza la transacción, el cliente revierte el cambio en el estado local y, según el caso, informa al usuario. Por eso es imprescindible diseñar el camino de error antes de lanzar a producción, no como un agregado posterior.
¿Esto funciona offline?
En buena medida, sí. Como los datos viven en IndexedDB, la app puede leer y permitir ediciones sin conexión, encolando las transacciones para enviarlas cuando la red vuelva. El grado de soporte offline depende de cómo el sync engine resuelva conflictos al reconectar.
¿Sirve este enfoque para una app con miles de usuarios concurrentes?
Sí. Linear particiona su tabla de issues en cientos de fragmentos en PostgreSQL y usa Redis como bus de eventos y caché para coordinar la propagación de deltas. La arquitectura local-first descarga trabajo del servidor justamente porque las lecturas frecuentes se resuelven en el cliente.
Referencias
- performance.dev — Desglose técnico original de Dennis Brotzky sobre cómo Linear logra su velocidad.
- linear.app — Artículo oficial de Linear sobre el escalado de su sync engine.
- developer.mozilla.org — Documentación de la API de IndexedDB en MDN.
- mobx.js.org — Documentación oficial de MobX y su modelo de reactividad granular.
- inkandswitch.com — Ensayo fundacional sobre los principios del software local-first.
- tanstack.com — TanStack Query, librería para updates optimistas y caché de datos.
📱 ¿Te gusta este contenido? Únete a nuestro canal de Telegram @programacion donde publicamos a diario lo más relevante de tecnología, IA y desarrollo. Resúmenes rápidos, contenido fresco todos los días.
Top comments (0)