jakearchibald

Activity

Stars and mentions · Last 30 days, Oct 6 – Nov 4

GitHub stars0
Mentions3
01234Oct 6Oct 20Nov 4
UTC days · repositories jakearchibald owns1 repository

Top repositories

jakearchibald’s repositories ranked by new forks, as on trending.

Loading

Contributes to

Repositories outside jakearchibald’s account where jakearchibald opened or reviewed pull requests in the past 365 days.

Who noticed jakearchibald

Everyone who starred or forked jakearchibald’s repositories since the archive began in 2011.

Notable accounts

Ordered by the stars their own repositories have received.

Biggest fans

Accounts that starred the most of these repositories.

Breakout weeks

Each repository’s best week of new stars, with the release published around it.

Mentions

3posts
Nov 50 posts
  1. Resilience Is the New UX: Building Offline-First Real-Time Apps That Survive Network Failure Originally published on tamiz.pro. The era of assuming a stable network is over. Users move through tunnels, elevators, and spotty mobile coverage, yet they expect real-time collaboration to feel instant. The gap between optimistic UI updates and server truth is where UX dies. Offline-first is not a feature; it's an architectural stance that treats network failure as a normal state, not an exception. This deep-dive explores the core principles, data structures, and sync strategies you need to build real-time apps that don't fall apart when the network does. Table of Contents - [1. The Offline-First Mindset](#1-the-offline-first-mindset) - [2. Core Principles: Local-First, Sync-Later](#2-core-principles-local-first-sync-later) - [3. Data Layer: Local Storage and IndexedDB](#3-data-layer-local-storage-and-indexeddb) - [4. Sync Engines: The Heart of Offline-First](#4-sync-engines-the-heart-of-offline-first) - [5. Conflict Resolution with CRDTs](#5-conflict-resolution-with-crdts) - [6. Real-Time Transport: WebSockets and Fallbacks](#6-real-time-transport-websockets-and-fallbacks) - [7. Practical Implementation: A Collaborative Editor Skeleton](#7-practical-implementation-a-collaborative-editor-skeleton) - [8. Testing Network Resilience](#8-testing-network-resilience) - [9. Production Considerations and Trade-offs](#9-production-considerations-and-trade-offs) - [Frequently Asked Questions](#frequently-asked-questions) 1. The Offline-First Mindset Traditional real-time apps (like chat or collaborative tools) rely on a persistent connection to a server. When the network blips, the UI freezes, the user loses context, and the app becomes useless. Offline-first flips this: the client is the source of truth for user interactions. All writes go to a local store first, then sync to the server when connectivity allows. This is the same philosophy behind local-first software—an approach that prioritizes user control and resilience. The key shift is from server-authoritative to client-authoritative with eventual consistency. Users see their changes immediately (optimistic updates), and the system reconciles differences in the background. This requires a robust data model, a sync protocol, and conflict resolution—not just a cache. 2. Core Principles: Local-First, Sync-Later Implementing offline-first means adhering to a few non-negotiable principles: - Local writes are immediate: Never block the UI on a network round-trip. Write to local storage (IndexedDB, SQLite, etc.) synchronously or with minimal latency. - Sync is asynchronous and retryable: The sync engine should handle intermittent connectivity, exponential backoff, and partial failures. - Conflicts are expected: Multiple devices or users can modify the same data while offline. The system must merge changes deterministically. - Network is a transport, not a dependency: WebSockets, HTTP, or any transport can be used, but the app must function without them. This mindset influences every layer: from the data model (must support versioning) to the UI (must show sync status). 3. Data Layer: Local Storage and IndexedDB For web apps, IndexedDB is the de facto standard for structured local storage. It's asynchronous, supports transactions, and can store large amounts of data. But raw IndexedDB is verbose. Libraries like Dexie.js and idb provide a friendlier API. A critical design decision: store both data and metadata (revision, timestamp, device ID, etc.) to support sync. Here's a typical schema for a collaborative note: // Schema for a note in IndexedDB interface Note { id: string; // UUID title: string; content: string; updatedAt: number; // server timestamp (or local) revision: number; // monotonic revision number deviceId: string; // originating device deleted: boolean; // tombstone for deletes } When a user edits, you update the local record and increment the revision. The sync engine later sends the latest revision to the server. 4. Sync Engines: The Heart of Offline-First The sync engine is responsible for: - Tracking changes: Maintain a queue of pending mutations (create, update, delete). - Sending changes: Push local changes to the server. - Receiving changes: Pull remote changes and apply them. - Resolving conflicts: Merge conflicting changes. A common pattern is the outbox pattern: every write goes to a local outbox table firstcars. The sync engine processes the outbox in order, sending mutations to the server. On success, it clears the outbox entry. On failure, it retries with backoff. Here's a simplified sync engine in TypeScript: class SyncEngine { private outbox: Mutation[] = []; private isSyncing = false; async enqueue(mutation: Mutation) { this.outbox.push(mutation); await this.persistOutbox(); this.sync(); } async sync() { if (this.isSyncing) return; this.isSyncing = true; while (this.outbox.length > 0) { const mutation = this.outbox[0]; try { await this.sendMutation(mutation); this.outbox.shift(); await this.persistOutbox(); } catch (e) { // Network error: backoff and retry later await this.backoff(); break; } } this.isSyncing = false; } } For pulling changes, the server maintains a change log (or you use a sync protocol like Yjs or Automerge). The client periodically asks for changes since the last sync point (since timestamp or revision). 5. Conflict Resolution with CRDTs Conflict resolution is the hardest part. Two users editing the same field offline will produce divergent states. You have several options: - Last-write-wins (LWW): Simple, but loses data. Use with timestamps, but clocks can be unreliable. - Field-level merging: Merge different fields, but conflicts on the same field still need resolution. - CRDTs (Conflict-free Replicated Data Types): Data structures that mathematically guarantee convergence without coordination. For text, Yjs uses a CRDT that allows concurrent edits to merge seamlessly. Yjs is the de facto standard for collaborative text editing. It uses a binary encoding and a sophisticated algorithm that preserves user intent. For example, two users typing in the same paragraph offline will both see their changes merged when they reconnect. For application state (like a JSON object), you can use Automerge or Yjs' Y.Map. These are more complex but provide true conflict-free merging. Here's how to use Yjs with a WebSocket provider: import * as Y from 'yjs'; import { WebsocketProvider } from 'y-websocket'; const doc = new Y.Doc(); const provider = new WebsocketProvider('ws://localhost:1234', 'my-room', doc); provider.on('status', (event) => { console.log('Sync status:', event.status); // 'connected' or 'disconnected' }); const ytext = doc.getText('content'); ytext.insert(0, 'Hello, offline world!'); Yjs handles local persistence via y-indexeddb, so you can reload the page and still have your changes. 6. Real-Time Transport: WebSockets and Fallbacks WebSockets are great for low-latency real-time communication, but they can drop. For offline-first, you need a transport that can recover. Use WebSockets as the primary channel, but have a fallback to HTTP long-polling or SSE (Server-Sent Events) for when WebSockets fail (e.g., behind some proxies). Libraries like socket.io provide automatic fallback to polling and reconnection. However, for offline-first, the sync engine should be independent of the transport. The transport is just a pipe for mutations and updates. When the connection is lost, the client should buffer events locally and replay them on reconnect. This is similar to how chat apps queue messages. 7. Practical Implementation: A Collaborative Editor Skeleton Let's build a minimal offline-first collaborative note app using Yjs, IndexedDB, and a Node.js server. We'll use y-websocket for sync and y-indexeddb for persistence. Prerequisites - Node.js 18+ - npm - Basic knowledge of TypeScript and Express Step 1: Set up the project mkdir offline-first-editor cd offline-first-editor npm init -y npm install yjs y-websocket y-indexeddb express ws Step 2: Create the server (server.js) const express = require('express'); const { WebSocketServer } = require('ws'); const { setupWSConnection } = require('y-websocket/bin/utils'); const app = express(); const server = app.listen(1234); const wss = new WebSocketServer({ server }); wss.on('connection', (conn, req) => { setupWSConnection(conn, req); }); console.log('Server running on ws://localhost:1234'); Step 3: Create the client (index.html and client.js) In index.html, include a contenteditable div and the client script. <!DOCTYPE html> <html> <head> <title>Offline-First Editor</title> </head> <body> <div id="editor" contenteditable="true" style="border:1px solid #ccc; height:300px;"></div> <div id="status">Offline</div> <script src="client.js" type="module"></script> </body> </html> In client.js: import * as Y from 'yjs'; import { WebsocketProvider } from 'y-websocket'; import { IndexeddbPersistence } from 'y-indexeddb'; const doc = new Y.Doc(); const provider = new WebsocketProvider('ws://localhost:1234', 'notes', doc); const persistence = new IndexeddbPersistence('notes', doc); // Sync status provider.on('status', (e) => { document.getElementById('status').textContent = e.status === 'connected' ? 'Online' : 'Offline'; }); // Bind the Y.Text to the contenteditable div const ytext = doc.getText('content'); const editor = document.getElementById('editor'); const render = () => { editor.innerText = ytext.toString(); }; ytext.observe(render); render(); // Handle user input editor.addEventListener('input', () => { const text = editor.innerText; if (text !== ytext.toString()) { // Replace the entire text (simplified; for production use y-prosemirror or y-quill) ytext.delete(0, ytext.length); ytext.insert(0, text); } }); Run the server with node server.js and open index.html. Try editing, then turn off Wi-Fi, keep editing, and reconnect. Your changes sync seamlessly. 8. Testing Network Resilience Testing offline behavior is crucial. Use tools like Chrome DevTools' Network Throttling to simulate offline and high-latency conditions. For automated tests, consider: - Jest with fake timers to test backoff logic. - Cypress with network stubbing to simulate disconnects. - Puppeteer to emulate offline mode. A simple test for the sync engine: // sync.test.ts import { SyncEngine } from './sync'; it('retries on failure', async () => { const engine = new SyncEngine(); let attempts = 0; engine.sendMutation = async () => { attempts++; if (attempts < 3) throw new Error('Network down'); }; await engine.enqueue({ type: 'update', id: '1', data: {} }); expect(attempts).toBe(3); }); 9. Production Considerations and Trade-offs Offline-first is powerful but not free. Consider these trade-offs: - Complexity: Conflict resolution and sync logic add significant complexity. - Data consistency: Eventual consistency means users might see slightly different states temporarily. - Storage limits: IndexedDB has quotas; large data sets need careful management. - Security: Offline data is on the device; encrypt sensitive data at rest. When to use offline-first: - Collaborative apps (editors, whiteboards) - Field work apps (inventory, inspections) - Messaging apps (already do this) When not to use: - Banking transactions (require strong consistency) - Real-time dashboards (data is ephemeral) Frequently Asked Questions Q: Can I use CRDTs for non-text data like forms? Yes. Yjs provides Y.Map and Y.Array for structured data. Automerge is another option that works on JSON-like data. For simple fields, LWW with server timestamps might suffice. Q: How do I handle file uploads offline? Queue the file metadata and upload the blob when online. Use IndexedDB to store the blob, then sync with a dedicated upload queue. Ensure you handle partial uploads with resumable upload protocols. Q: What if two users delete the same item offline? CRDTs handle this with tombstones. Both deletes are merged, and the item is deleted once. For LWW, the last delete wins. --- Building offline-first real-time apps is a shift in mindset. By treating the network as a fallible transport and the client as a resilient store, you create apps that respect the user's time and context. Start small—add local persistence to one feature, then layer sync. The investment pays off in user trust and engagement. For more insights on building resilient systems, check out Tamiz's Insights.

  2. That simply isn't true. Here it is in the data for last year's interop ranking site github.com/jakearchibald/interop-stack-rank/blob/… And here is the interop proposal it's referencing interop#1012.

  3. JPEG XL vs AVIF: Which Image Format Should You Actually Ship? For years the answer to "which modern image format should I use?" was easy. AVIF, full stop. JPEG XL was the promising codec Google helped design and then declined to ship in Chrome, and the whole conversation got poisoned by conspiracy theories about why. That changed this month. JPEG XL has now landed in all major browsers, including Chrome. The format war everyone assumed was over just reopened, and the first serious piece of post-launch evidence is a monster benchmark by Jake Archibald, who has been writing AVIF comparisons since 2020. He ran four encoders across photos, screenshots, illustrations, transparency, HDR, and lossless cases, measuring file size, encode time, memory, and decode speed. A disclosure before the numbers: I have not run these benchmarks myself. Every figure below comes from Archibald's published results, with the methodology and raw data linked in his post. What I have added is the part he intentionally skips: a decision matrix you can apply to your own image pipeline, and the commands to verify it with your own images. The headline result: AVIF still wins most lossy photos For lossy photos, AVIF almost always produces smaller files. At default encoder effort, the best JPEG XL version was typically 15 to 25 percent larger than the equivalent AVIF at the same perceptual quality. At maximum effort the gap narrows to roughly 6 to 20 percent. On a typical F1 test photo at high quality (SSIMULACRA2 score of 70), AVIF hit 41.4 kB while libjxl needed 58.4 kB and the newer jixel encoder 46.6 kB. The encoder matters more than most people realize. The interesting twist in this benchmark is not just JPEG XL vs AVIF, it is libjxl vs jixel, a newer second JPEG XL encoder. jixel usually beats libjxl on file size, except on images with detailed transparency, where it falls back to lossless alpha encoding and balloons. AVIF is also faster and lighter on balance. jixel encodes faster than AVIF, but its files are bigger, so to actually match AVIF's output it needs its slowest setting, which took 18 to 27 times longer in the benchmark. Decode time matters even more for your users: JPEG XL typically takes 2 to 3 times as long to decode as AVIF at medium quality, and that cost lands on every single page view, on every device. Where JPEG XL genuinely beats AVIF If the story were only lossy photos, this article would be two paragraphs long. It is not, and here the benchmark delivers real surprises. Lossless compression is a JPEG XL blowout. For lossless photos, lossless WebP came out 9 to 17 percent larger and lossless AVIF 30 to 120 percent larger than JPEG XL. If you store or serve bit-exact images, the calculus flips completely. Recompressing existing JPEGs is JPEG XL's killer feature. JPEG XL can make existing JPEG files 9 to 20 percent smaller without any additional quality loss, and decode them as fast or faster, because it can wrap the original JPEG coefficients instead of re-encoding pixels. For anyone sitting on years of legacy JPEG archives, that is free bandwidth on images you never have to regenerate. Progressive loading is nearly free in JPEG XL. jixel recently added progressive encoding, and at its slowest settings the preview support costs little or nothing in file size. Firefox and Chrome both render progressive JPEG XL previews; Safari downloads the full image before showing anything. Meanwhile AVIF turned out to support progressive rendering too, currently Chrome-only, with more control over the preview and faster decode. This space is moving on both sides. Where AVIF is not even close Three categories ended decisively. Screenshots and illustrations: lossy AVIF was 30 to 90 percent smaller than JPEG XL at default effort, and 20 to 70 percent smaller at maximum effort. And you do not need lossless formats for screenshots; lossy AVIF handles them well unless you genuinely need pixel-exact output. Transparency: libjxl files were 31 to 44 percent larger at default effort and 61 to 70 percent larger at maximum. jixel was worse, because it always encodes alpha losslessly, landing 2 to 3 times the size of AVIF. HDR: JPEG XL lacks browser support for gain maps, which is how AVIF carries HDR brightness metadata. The format supports them on paper, but browser decoders do not, and the encoding tooling is experimental. One more finding worth internalizing: animated image formats lose to video, every time. In Archibald's clip test, animated AVIF hit 725 kB where animated JPEG XL needed 2.17 MB for the same quality score, and both lose to a plain <video muted autoplay loop>. The decode cost lands on your users, not your build server It is tempting to read "JPEG XL encodes fine with jixel" and conclude the formats are equivalent and you can pick either. The encode step is the cheap part. Encoding happens once per image on your build server. Decoding happens on every visitor's device, every time the image loads, including cache revalidations and bfcache misses. JPEG XL typically takes 2 to 3 times as long to decode as AVIF at medium quality, and the gap only narrows at very high quality settings, where Chrome can sometimes decode JPEG XL faster. On a flagship phone nobody notices either way. On a three-year-old mid-range Android device on a congested network, decode time is part of your Largest Contentful Paint, and LCP is part of how your pages rank and convert. A format that wins file size by 10 percent but doubles decode time can easily be a net loss for the user-perceived speed of an image-heavy page. The benchmark measured decode in real browsers, Firefox, Chrome, and Safari, on the actual decoded images, which is the right way to do it. Treat any codec comparison that only reports encode numbers with suspicion. One reassuring finding: quality settings behave predictably If you automate encoding in CI, you depend on a quality flag meaning roughly the same thing across images. The benchmark checked this explicitly, and the result is that quality settings are reliable for all four encoders: with a single quality setting, output quality varies somewhat between images, but all encoders vary in the same direction and in ways that seem reasonable. You will not get a 95 kB surprise out of a pipeline configured for 50 kB targets. That makes a build-time AVIF pass, with a JPEG XL fallback attempt only for outlier files, a safe thing to automate. The 4:2:0 trap A subtle but expensive finding: AVIF's 4:2:0 mode, which halves color resolution, should not be your default. It rarely reduces file size in practice, and it makes higher quality targets harder to reach. Many tutorials and CI pipelines still use it because it encodes and decodes faster. The benchmark's advice is to stick with 4:4:4 unless you have time to compare both per image. If your current pipeline was configured years ago, this single flag may be costing you quality for zero savings. Your decision matrix Here is the whole benchmark compressed into one checklist you can save: - Default for photos on the web: AVIF at 4:4:4, with the highest encoder effort you are willing to wait for. - When AVIF produces an unusually large file: try JPEG XL via jixel on that image; there are real outliers where it wins. - Screenshots, diagrams, illustrations: lossy AVIF. Drop the lossless habit unless you need bit-exact output. - Images with transparency: AVIF, by a wide margin. - Legacy JPEG archives: JPEG XL lossless recompression, 9 to 20 percent smaller, no extra loss. - Bit-exact lossless storage: JPEG XL, the clear winner over lossless WebP and lossless AVIF. - HDR imagery: AVIF, because of gain map support. - Animated images: use video. Both animated formats are worse and this benchmark shows by how much. Verify it with your own images Both authors of the benchmark and the counter-arguments agree on one thing: codec discourse is full of misinformation, and your AI assistant will happily repeat it. Test with your own images. Archibald released his benchmarking harness on GitHub, using avifenc 1.4.2, libjxl 0.12.0, jixel 0.4.2, and cwebp 1.6.0. For a quick manual check with libavif's tools: # AVIF at 4:4:4, quality 50 avifenc --yuv 444 -q 50 -j all photo.jpg photo.avif # JPEG XL with libjxl, distance 1.0 (roughly high quality), effort 7 cjxl -d 1.0 -e 7 photo.jpg photo.jxl Then compare file sizes at visually identical quality. His benchmark targeted SSIMULACRA2 scores, a perceptual quality metric from the JPEG XL team, so comparing a 41 kB AVIF against a 58 kB JPEG XL at the same score is the fair way to do it, not at matching quality flags. In your HTML, both formats work inside a <picture> element so older browsers fall back gracefully: <picture> <source srcset="photo.avif" type="image/avif" /> <source srcset="photo.jxl" type="image/jxl" /> <img src="photo.jpg" alt="Product photo" width="1598" height="1065" /> </picture> The benchmark encoded test images at 1598 pixels wide, twice the maximum display width, which keeps them sharp on high density screens. That is a reasonable target width for content images too. So which one should you ship? For web use cases, as of October 2026: default to AVIF 4:4:4 at high effort, and reach for JPEG XL when you have a JPEG archive to shrink or a lossless case, or when AVIF underperforms on a specific image. Both encoders have improved massively in recent months, so this recommendation has an expiration date, and the honest move is to re-run the numbers on your own assets before a big migration. What I find genuinely interesting here is the meta-lesson. JPEG XL was written off for three years because of a browser politics fight, and a second independent implementation showing up changed the benchmarks more than any codec committee decision could. Competition between encoders is now real, and whichever format "wins," the files your users download keep getting smaller. I write about web performance, developer tooling, and AI infrastructure every week. Subscribe, it is free. Do you serve AVIF in production today, or are you still on WebP with a <picture> fallback chain? Have you measured what a format switch actually saved you? Sources: - JPEG XL vs AVIF for web use-cases, Jake Archibald, October 2026 - image-codec-bench, the benchmarking harness and methodology - jixel, the second JPEG XL encoder - The case against JXL, the AVIF-side counter-argument

    2 reactions

Achievements

Awards earned by the repositories jakearchibald owns. Select an award to embed its badge.

Gold
01st place finishes
Silver
02nd place finishes
Bronze
23rd place finishes
Awarded repositories
1Owned by jakearchibald now

Repositories

Ordered by gold, then silver, then bronze.

  1. sprite-cowBest: 3rd by stars, 40 stars002

Every award

Top-three finishes of these repositories in completed trending days and weeks, newest first.

  1. sprite-cow
  2. sprite-cow

A repository earns an award for finishing first, second or third in a completed trending day or week, ranked by new stars, new forks or pull request authors. Awards follow a repository through renames and transfers and count toward its current owner. Weeks run Monday to Sunday, UTC.