The Core Insight: Browser as Database

Linear inverts the traditional web app architecture. Instead of waiting for an HTTP round-trip, the UI reads from an in-memory store backed by IndexedDB. Mutations apply locally, then async push to the server via WebSocket. This eliminates spinners entirely.

// Traditional
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();
}

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

The first line updates a MobX observable; issue.save() queues a transaction that the sync engine batches and flushes. The UI re-renders synchronously off local data. Co-founder Tuomas said at a 2024 conference: "Literally the first lines of code that I wrote was the sync engine."

Bundler Evolution: 50% Less Code

Linear rewrote its build pipeline four times: Parcel → Rollup → Vite → Rolldown. Each migration aimed to reduce shipped JavaScript and CSS. Results from their blog posts:

  • 50% less code shipped
  • 30% smaller after compression
  • Cold-cache page loads 10-30% faster
  • Time-to-first-paint of active-issues view dropped 59% on Safari
  • Memory usage dropped 70-80%

Key decisions: targeting only modern browsers (no polyfills, no ES5), aggressive dead-code elimination, and code splitting. Linear ships ~21 MB of minified JavaScript but splits it into hundreds of route-level chunks fetched on demand.

// vite.config.ts (reconstructed)
export default defineConfig({
  plugins: [react()],
  build: {
    target: "esnext",
    cssMinify: "lightningcss",
    modulePreload: { polyfill: false },
    rollupOptions: {
      output: {
        manualChunks(id) {
          if (id.includes("node_modules")) {
            const pkg = id.match(/node_modules\/([^/]+)/)?.[1];
            if (pkg) return `vendor-${pkg}`;
          }
        },
      },
    },
  },
});

Preloading: Collapse the Waterfall

After chunking, the next problem is the import waterfall. Linear solves this with tags in the of their index.html. Before any JavaScript runs, the browser sees the entire dependency list and fires parallel requests.


The `crossorigin` attribute matches the entry script, so the browser reuses cached fetches. The cold-load timeline collapses from sequential waterfall to single parallel batch.

## Service Worker: Precache Everything

Linear's service worker has a precache manifest of ~1,200 hashed assets (route chunks, icons, fonts). It pulls them down lazily after the first page load. Within seconds of hitting the login page, the full app is cached and subsequent loads are instant.

## The Stack

Linear uses a simple stack: React, TypeScript, MobX, Postgres, a CDN. No edge database, no React Server Components.

- **Frontend**: React + react-dom, MobX, ProseMirror + y-prosemirror (rich text), Radix UI, Emotion + StyleX, Comlink for worker RPC, idb for IndexedDB, graphql-request, Sentry
- **Backend**: Node.js + TypeScript, PostgreSQL on Cloud SQL (issues table partitioned 300 ways), Memorystore Redis (event bus + cache + sync cursors), turbopuffer (vector db), Kubernetes on GCP, Cloudflare Workers (edge proxy)
- **Other clients**: Desktop: Electron; Mobile: Swift (iOS) + Kotlin (separate reimplementation)

## Animations and Design

Animations are designed to feel immediate. The article mentions that Linear uses `Inter Variable` font with `font-display: swap` and inline SVG sprites. The goal is to hide network latency from the user entirely.

## Why It Matters

Most web apps feel slow because the UI waits for each network request. Linear's approach proves that optimistic updates and local-first architecture can make a complex app feel native. You don't need a custom sync engine; libraries like Tanstack Query or SWR with optimistic updates can get surprisingly close.

## Next Steps

If you want to apply these techniques:
1. Start with optimistic mutations in your data-fetching library.
2. Drop legacy browser support and target `esnext`.
3. Use `modulepreload` to parallelize chunk loading.
4. Add a service worker to precache assets after first load.