
2026/06/08 4:01
Linear がなぜこれほど速いのか?技術的な解説
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
リニア革命:ウェブアプリケーションのパフォーマンス革新
リニアは、データベースをブラウザ内に完全に実行する(IndexedDB を使用)ことで、従来の CRUD アプリのデータ読み込み時間である約 300ms を数ミリ秒に短縮します。この「ローカルファースト」アーキテクチャでは、標準的なネットワークループが逆転し、デバイス上で変更を即時適用し、WebSocket を経由で非同期でデルタをプッシュすることで、アップデート待ちの地味な网络待ち時間を排除します。共同創設者のトゥオマス・カンカレは、この自社工程エンジンをゼロから構築することを強く推奨しました。タンスタッククエリや SWR などの一般的な楽観的な更新ライブラリを使用せず、サーバーを単なる同期ターゲットとして厳密に扱うアプローチを採用しています。JavaScript のサイズを最小限に抑えるため(圧縮後の JS は約 21MB にまで削減され、ルートレベルのチャンクに分けられている)、チームはバンドルパイプラインを 4 回も移行しました(Parcel → Rollup → Vite → Rolldown)。これにより、配送されるコード量は約 50% 削減されました。重要資産には、フォント(単一の可変 Inter ファイル)、数百のルートチャンクが含まれており、サービスワーカーと
<head/> に設定された並行モジュールプレロードリンクを通じて事前キャッシュされます。これにより、オフライン時や繰り返し訪問時でも即座にレンダリングが可能になります。さらに、重要な CSS、JavaScript、認証ロジックは HTML に直接埋め込まれており、認証にはセッショントークンの即時取得ではなく、ローカルストレージ内の存在を確認する方式を採用しています。该系统は、50 件のイシューリストが変更された場合、わずか 50 セルだけを更新するなどの粒度の細かなリレンダを達成します。これは、データをプロパティごとの MobX オブザーバブルに水浸げすることで実現されており、標準的なフレームワークでは追いつけない優れた速度優位性を保証しています。本文
【事例研究】数ミリ秒で課題をアップデート:Linear がなぜこれほど高速なのか?
Linear(リニア)の「課題更新」という動作は、従来の CRUD アプリが約 300ms かかるのに対し、わずか数ミリ秒で完了します。 Linear のような速度を実現する「銀色の弾丸」のような単一のパターンはありません。彼らは正しく構築された土台から始め、多数の技術的・設計的な判断を積み重ねてきた結果です。
この記事では、Linear が高性能な Web アプリを実装するためのテクニックとアーキテクチャを整理します。 ※私は Linear での勤務経験はなく、直接コードを見ることはできません。ただし、Linear のアプリを個人で研究し、関連記事やカンファレンス登壇動画から得た知見に基づいています。
1. ブラウザ上のデータベース(Local-First Architecture)
従来の Web アプリの課題
多くの Web アプリは「ネットワーク待機」のループに縛られています:
- ユーザークリック
- ブラウザから HTTP リクエストへ
- サーバーで DB クエリを実行
- データ返信
- ブラウザで描画
この過程では、数百ミリ秒の間 UI がスピナーやスケリータ(骨組み表示)を示さざるを得ません。
Linear のアプローチ:ネットワークを排除する
Linear はこの関係性を逆転させます。UI が参照するデータベースはブラウザ上にあり、IndexedDB に保存されています。
- ローカル更新: ユーザー操作はまずクライアント側で適用されます。
- 非同期シンク: 変更内容は後からサーバーにプッシュされます(WebSocket で差分データを送信)。
重要なポイント: Linear パフォーマンスにおいて最も大きなボトルネックはネットワークです。あらゆるデータ送受信には数百 ms のコストがかかります。高速 Web アプリを作る鍵は、ユーザーが待つことのない設計をすることにあります。
コードによる比較:更新処理の仕組み
[従来]: サーバーを更新する場合
async function updateIssue({ issue }) { showSpinner(); // UI を待機させる(スピナー表示) const response = await fetch(`/api/issues/${issue.id}`, { method: "PATCH", body: JSON.stringify({ title: issue.title }), }); const updated = await response.json(); setIssue(updated); // UI 更新 hideSpinner(); // 待機終了 }
[Linear]: ローカル優先の更新処理
// 1. メモリ上のデータストア(MobX observable)を更新 issue.title = "Faster app launch"; // 2. サーバーへのトランザクションをキューイング(シンクエンジンがバックグラウンドで処理) issue.save(); // スピナーが出ないのは、UI が即座に反応するため
- Local-First: UI はメモリ上の更新に基づき即座に再描画されます。
- Optimistic Update: ネットワークリクエストの成功を仮定して先に状態を変更し、失敗すればロールバックします。
2. Linear スタックのポイント (Tech Stack)
Linear は複雑さを排除し、シンプルかつ強力な構成を採用しています。
| カテゴリ | テクノロジー・ライブラリ | 役割・理由 |
|---|---|---|
| Frontend | React, TypeScript | UI 実行環境と型付けによる一貫性 |
| MobX | 細粒度なオブジェクト再描画(Observables) | |
| Vite (Rollup) | ビルドツール(2025 年中盤現在:Rolldown-Vite) | |
| ProseMirror + Yjs | リッチテキストエディタ、ライブコラボレーション | |
| Radix UI primitives | パフォーマンスの高いコンポーネント構築 | |
| Emotion + StyleX | CSS アトミック化、スタイル管理 | |
IndexedDB () | クライアントサイドデータベース | |
| Backend | Node.js + TypeScript | サーバーロジックの一貫性 |
| PostgreSQL (Cloud SQL) | 300 パートへのパーティショニングでスケーラビリティ確保 | |
| Redis | イベントバス、キャッシュ、シンクキュー管理 | |
| Turbopuffer | 類似課題検出(ベクター DB) | |
| Infra | Cloudflare Workers | マルチリージョンエッジプロキシ |
| GCP Kubernetes | コンテナオーケストレーション | |
| Desktop | Electron (Chromium) | Web JS を活用したネイティブアプリ |
設計思想:シンプルさの美しさ
- クライアントサイドレンダリング (CSR): SSR(サーバーサイドレンダリング)の複雑性を排除。
やキャッシュヘッダーの考慮が不要になり、メンタルモデルがクリーンになります。window - 依存関係管理: 外部ライブラリは最小限にし、必要に応じて独自実装を好みます。
3. 瞬間的な読み込み体験の正体
バンドラーの進化 (Parcel → Rollup → Vite → Rolldown)
Linear はビルドパイプラインを何度か書き換えてきました。その最大の目的は**「送信されるコード量の削減」**です。
達成された最適化効果
- 送信コード: 50% 減少
- 圧縮サイズ: 30% 小型化
- コールドキャッシュロード: 10〜30% 高速化(Safari 基準で TTFP が 59% 改善)
- メモリ使用量: 70〜80% 減少
コード分割の重要性
Linear は依然として約 21MB のコードを送信しますが、**「攻撃的なコード分割」**を行っています。
- ラージチャンク化されず、数百のルートレベルチャンクに分割。
- オンデマンドでフェッチされるため、初期ロード量は劇的に減少。
Vite コンフィグ例(自動分割):
export default defineConfig({ plugins: [react()], build: { target: "esnext", // レガシー構演・ポリフィル不使用 cssMinify: "lightningcss", modulePreload: { polyfill: false }, rollupOptions: { output: { manualChunks(id) { // NPM パッケージごとに独立したチャンク化 (> 3KB は vendor-xxx にまとめない) if (id.includes("node_modules")) { const pkg = id.match(/node_modules\/([^/]+)/)?.[1]; if (pkg) return `vendor-${pkg}`; } }, }, }, }, });
プリローディング戦略
数百個のチャンクが存在する場合、ブラウザは並列で読み込みできない(ウォーターフォール構造になるため)です。Linear は
<head> タグ内で全ての必要なリソースを modulepreload で指定します。
<script type="module" src="entry.js"></script> <link rel="modulepreload" href="vendor-mobx.js"> <link rel="modulepreload" href="sync-websocket.js"> <link rel="modulepreload" href="database-manager.js"> <!-- ... 他多数のチャンクを並列で読み込み可能に -->
これにより、エントリーポイントがインポートに到達する頃には、全ての依存ライブラリがキャッシュされています。
サービスワーカーによるプレキャッシュ
Linear はサービスワーカーを使用して、ユーザーがまだ訪問していないビュー用ルートチャンクをバックグラウンドでキャッシュします。
- 初回アクセス: ログイン画面から数秒後にはフルアプリがキャッシュに格納される。
- 再訪・ナビゲーション: ネットワークリクエストを完全にスキップし、直接キャッシュから読み込み。
- オフライン機能: IndexedDB とシンクエンジンにより、接続なしでも課題の作成・編集が可能。
インラインスタイルと認証の最適化
インライン CSS: 初回描画に必要なクリティカル CSS を
<head> に埋め込むことで、ネットワークリクエストを排除。
認証フローの変更:
クッキーではなく、HttpOnly
の存在を確認。localStorage.ApplicationStore- データが存在すれば「既にログイン済み」と仮定し、即座にデータを描画。
- セッション切れは背景で検知され、その時だけログイン画面へ誘導(401 エラー対応)。
初回 JavaScript 実行時の処理例:
// ロードシェル(インライン JS)による初期設定 if (localStorage.getItem("ApplicationStore") === null) { document.documentElement.classList.add("logged-out"); } // ユーザーの設定(ダークモード、ウィンドウサイズ等)を復元し描画 const c = JSON.parse(localStorage.getItem("splashScreenConfig") || "{}"); if (c.bgSidebarColor) { /* スタイル適用 */ }
4. シンクエンジン(Sync Engine)の仕組み
Linear の速度には「3 つの柱」があります:
- データは既にそこにある
- 楽観的更新(ネットワーク待ちなし)
- ワン・デルタ、ワンセル(細粒度再描画)
1. データはローカルに保持される
アプリ起動時にサーバーから全データをフェッチしません。IndexedDB からメモリ上の MobX オブジェクトプールへロードします。「読み込み中」というステートは不要です。
2. 楽観的更新
UI の変更を即座に反映させ、サーバーへの送信は非同期(バックグラウンド)で行います。
- サーバーエラーが発生しても、UI は一度も止まりません(ロールバックのみ)。
- ネットワークを待機しないため、ユーザー体験が劇的に向上します。
3. 細粒度再描画 (Finely Granular Updates)
Linear の各モデルプロパティは独自の Observable を持ちます。更新されたフィールドのみが描画され、親要素(リストやサイドバー)全体が再描画されることはありません。
- 50 つの課題を同時更新しても: リスト全体ではなく、変更された「50 セル」のみが描画されます。
- このため、複数のユーザーが同時に編集しても滑らかな動作を保てます。
5. 速度を前提としたデザイン(Keyboard First Design)
エンジニアリングだけでなく、デザインの設計も速度に直結します。
ショートカットの浸透
Linear では「すべてのアクションにショートカット」が存在します:
- ⌘K: グローバルコマンドパレットを開く(検索による全機能アクセス)。
- 単一文字キーで頻出操作を実行可能。
- マウス依存を排除し、初心者の手取り間も短縮。
ショートカットの恩恵:
- エンジニアリング速度:1 つのアクションを高速化。
- デザイン速度(ショートカット):各アクションまでの「パス長」を短縮。
- 複利効果:毎日使用するツールにおいて、マウスとキーボードのパフォーマンス差は累積して拡大します。
6. アニメーションの最小化
アニメーションも性能に直結します。Linear はアニメーションのプロパティ選択に厳格です。
制限されるべきプロパティ(レイアウトトリガー)
,width
,height
,margin
など、ブラウザが再計算を強制するプロパティは決してアニメーションしない。padding- これらを animating すると、全リストのレイアウト更新が発生し、劇的に遅くなります。
おすすめのプロパティ(合成/描画トリガー)
,transform
: GPU 利用でメインスレッドを解放。opacity
,color
: レイアウト変更なしでのピクセル更新のみ。background-color
Linear の CSS プロパティ例:
/* 推奨: transform, opacity, color などは高速 */ .row:hover { background-color: var(--color-bg-hover); transition: background-color 0.12s; } .icon-arrow { transform: translateX(0); transition: transform 0.15s; } /* 避けるべき: margin, padding などのレイアウト変更 */ // .row:hover { // margin-left: 2px; /* 全要素再計算を招く */ // }
アニメーションの方向性
- Enter: 即座に(0ms〜150ms)
- Exit: 滑らかにフェードアウト(非対称なタイミング)。
- 期間: 業界標準(200〜350ms)よりも短く設定し、アプリの軽快さを維持。
まとめ:なぜ Linear はこれほど速いのか?
Linear の高速さは、単一のパターンによるものではなく、正しい判断を数多く積み重ねた結果です。そのアーキテクチャは以下の形をしています:
- サーバーは情報源で同期ターゲット: データ更新のトリガーはローカル。
- データベースはブラウザに居住: IndexedDB を活用し、ネットワーク待機を排除。
- 楽観的更新と細粒度再描画: 50 件の更新も、リスト全体ではなくセル単位での描画のみ。
- 最小限のコード配送とプレキャッシュ: 初回ロード時の JavaScript/CSS 量を削減。
- キーボードファースト設計: ショートカットとコマンドパレットによる高速操作パス。
- 制限されたアニメーション: GPU の恩恵を受け、レイアウト再計算を避ける。
Linear は Next.js や特別なフレームワークを用いていません。React, TypeScript, MobX という「あるべきもの」から始まり、単純さを目指しました。
**「難しいのは実装ではなく、年々コードベースが成熟し、新しい制約に押しやられていながらも職人への献身」**です。
Linear のような速度を実現するには、ネットワークリクエストを可能な限り排除し、ユーザーが待つことのない設計を行う必要があります。ぜひご自身のプロジェクトにも応用してみてはいかがでしょうか。