How's Linear so fast? A technical breakdown

How’s Linear so fast? A technical breakdown

Linear 为什么这么快?技术深度解析

Dennis Brotzky · May 3, 2026

A few milliseconds is all it takes to update an issue in Linear. A traditional CRUD app doing the same thing takes about 300ms. How do they do it? There’s no secret silver bullet to performance. The reality is that it’s built from the ground up on the right foundation, then improved by countless decisions. My goal is to walk through some of the techniques that make Linear feel the way it does and help you implement the same.

在 Linear 中更新一个任务只需几毫秒。而传统的 CRUD(增删改查)应用完成同样的操作通常需要约 300 毫秒。他们是怎么做到的?性能提升并没有什么秘密武器。事实是,它从一开始就建立在正确的架构基础之上,并通过无数的决策不断优化。我的目标是剖析一些让 Linear 拥有如此出色体验的技术,并帮助你实现同样的效果。

What I’ll cover

本文涵盖内容

  • Database in the browser

  • Making the first load feel instant

  • The sync engine

  • Designed for speed

  • Animations

  • 浏览器端数据库

  • 让首次加载感觉瞬间完成

  • 同步引擎

  • 为速度而设计

  • 动画效果

A quick disclaimer: I’ve never worked at Linear and have never seen their code. Everything I share comes from my personal experience, studying their app, reading their blog posts, or watching their conference talks. I simply love building web apps and have been using Linear since their beta launch. Also, the article’s hero image comes from a video by Meg Wayne, whose work for Linear is phenomenal.

简要声明:我从未在 Linear 工作过,也从未见过他们的代码。我所分享的一切均源于我的个人经验、对他们应用的钻研、阅读他们的博客文章或观看他们的会议演讲。我单纯热爱构建 Web 应用,并且从 Linear 测试版发布起就开始使用它。此外,本文的题图来自 Meg Wayne 的视频,她在 Linear 的工作非常出色。

Database in the browser

浏览器端数据库

Most web apps live inside the same loop. The user clicks. The browser fires an HTTP request. A server queries a database and sends it back. The browser repaints. The end result is a spinner, a skeleton, or a frozen UI for a few hundred milliseconds while the app waits on the network.

大多数 Web 应用都运行在同一个循环中:用户点击,浏览器发送 HTTP 请求,服务器查询数据库并返回结果,浏览器重绘。最终的结果是,在应用等待网络响应的几百毫秒内,用户会看到加载转圈、骨架屏或卡顿的界面。

Linear inverts the traditional relationship. The actual database the UI reads from is in the browser, in IndexedDB. Mutations apply locally first, then asynchronously push to the server, which broadcasts deltas back to other clients via WebSocket.

Linear 颠覆了这种传统关系。UI 读取的实际数据库位于浏览器内部的 IndexedDB 中。变更操作首先在本地应用,然后异步推送到服务器,服务器再通过 WebSocket 将增量更新广播给其他客户端。

In my opinion, this is the most critical piece to Linear’s performance. When your goal is to build a fast web app the biggest bottleneck you will fight is the network. Any data sent between the client and server costs hundreds of milliseconds. The best approach is to eliminate the need for a network request entirely: which is exactly what Linear does.

在我看来,这是 Linear 性能表现中最关键的部分。当你旨在构建一个快速的 Web 应用时,你所面临的最大瓶颈就是网络。客户端和服务器之间传输的任何数据都会消耗数百毫秒。最好的方法是完全消除对网络请求的需求:而这正是 Linear 所做的。

I’ll be repeating this a lot, but the secret to building incredible web apps is by hiding all the network requests from the user. The more loading states you can avoid the better.

我会反复强调这一点:构建出色 Web 应用的秘诀在于对用户隐藏所有的网络请求。你能避免的加载状态越多越好。

Here’s an example of how simple Linear’s requests are: 以下是 Linear 请求处理有多简单的示例:

// A traditional web app updating the server
async function updateIssue({ issue }) {
  showSpinner();
  const response = await fetch(`/api/issues/${issue.id}`, {
    method: "PATCH",
    body: JSON.stringify({ title: issue.title }),
  });
  const updated = await response.json();
  setIssue(updated)
  hideSpinner();
}

// vs Linear
issue.title = "Faster app launch";
issue.save();

The first line, issue.title = "Faster app launch", updates an in-memory datastore (MobX observable in Linear’s case). The second line, issue.save();, queues a transaction that their sync engine batches and flushes to the server. The key here is that the UI re-renders synchronously off the local, in-memory, update. There are no spinners because there is nothing to wait for because the data is synced in the background. This is the magic of treating the browser as the database for each user.

第一行 issue.title = "Faster app launch" 更新了一个内存中的数据存储(在 Linear 的案例中是 MobX 可观察对象)。第二行 issue.save(); 将一个事务加入队列,由他们的同步引擎进行批处理并刷新到服务器。这里的关键在于,UI 是基于本地内存中的更新同步重新渲染的。因为数据是在后台同步的,无需等待任何响应,所以不会出现加载转圈。这就是将浏览器视为每个用户数据库的魔力所在。

Tuomas, one of Linear’s co-founders, said this at a conference in 2024: ‘Literally the first lines of code that I wrote was the sync engine, which is very uncommon to what you usually do when you’re a startup.’ From day one, Linear knew the approach they wanted to take and the tradeoffs it would take.

Linear 的联合创始人之一 Tuomas 在 2024 年的一次会议上曾说:“我写下的第一行代码实际上就是同步引擎,这在初创公司中是非常罕见的。” 从第一天起,Linear 就明确了他们想要采取的方法以及为此需要做出的权衡。

I know most people won’t build a custom sync engine like Linear just to make their app feel fast and they don’t need to. For most use cases, libraries like Tanstack Query and SWR can get surprisingly close with optimistic updates. Most web apps feel slow because the UI waits for each network request to complete before updating state. For most usecases the network request will succeed so you should take advantage of that and optimistically update your state.

我知道大多数人不会为了让应用变快而去构建像 Linear 那样的自定义同步引擎,而且也没必要。对于大多数用例,像 Tanstack Query 和 SWR 这样的库通过“乐观更新”(optimistic updates)可以达到惊人的效果。大多数 Web 应用感觉缓慢,是因为 UI 在更新状态前必须等待每个网络请求完成。对于大多数场景,网络请求通常会成功,因此你应该利用这一点,乐观地更新你的状态。

// optimistic mutation with SWR
mutate(
  `/api/issues/${issue.id}`,
  { ...issue, title: "Faster app launch" },
  false
);

// vs Linear
issue.title = "Faster app launch";
issue.save();

The key idea is simple: UI responsiveness should not depend on network latency. Users perceive speed based on how quickly the interface reacts, not how quickly the server responds.

核心理念很简单:UI 的响应速度不应依赖于网络延迟。用户对速度的感知取决于界面反应有多快,而不是服务器响应有多快。

Optimistic requests is one of the highest leverage improvements you can make: 乐观请求是你所能做的最高杠杆率的改进之一:

  • eliminate unnecessary spinners (消除不必要的加载转圈)
  • update state immediately (立即更新状态)
  • validate in the background (在后台进行验证)
  • rollback only if needed (仅在必要时回滚)

Linear’s foundation is based on this exact principal and it makes the app feel native and fast. Linear 的基础正是建立在这一原则之上,这使得该应用感觉既原生又快速。

A peek into Linear’s stack

一窥 Linear 的技术栈

Linear is built on the simplest stacks you can find: React, TypeScript, MobX, Postgres, a CDN. There’s no edge database, no React Server Components, or no fancy framework.

Linear 构建在你能找到的最简单的技术栈之上:React、TypeScript、MobX、Postgres 和 CDN。没有边缘数据库,没有 React Server Components,也没有花哨的框架。

  • Frontend: React + react-dom, MobX, TypeScript, Rolldown-Vite, ProseMirror + Yjs, Radix UI, Emotion + StyleX, Comlink, idb, graphql-request, Sentry, Inter Variable.
  • Backend: Node.js + TypeScript, PostgreSQL (partitioned), Memorystore Redis, turbopuffer, Kubernetes on GCP, Cloudflare Workers.
  • Other clients: Electron (Desktop), Swift (iOS), Kotlin (Android).
  • Marketing: Next.js (static), styled-components, Inline SVG sprite.

(前端:React + react-dom, MobX, TypeScript, Rolldown-Vite, ProseMirror + Yjs, Radix UI, Emotion + StyleX, Comlink, idb, graphql-request, Sentry, Inter Variable。后端:Node.js + TypeScript, PostgreSQL (分区), Memorystore Redis, turbopuffer, GCP 上的 Kubernetes, Cloudflare Workers。其他客户端:Electron (桌面端), Swift (iOS), Kotlin (Android)。营销页面:Next.js (静态), styled-components, 内联 SVG 精灵图。)

The biggest standout to me is their decision to stick with client-side rendering. CSR often gets criticized for slow initial loads, but with the right architecture and design it can feel instant. I’m also a big fan of the simplicity it brings. 对我而言,最突出的一点是他们坚持使用客户端渲染(CSR)。CSR 常因首次加载缓慢而受到诟病,但通过正确的架构和设计,它完全可以做到瞬间加载。我也非常推崇它所带来的简洁性。