The first 50 tools worked great. Tool #51—a PDF form filler—crashed Safari because I assumed 2GB of WASM memory was standard. It's not. iOS kills tabs at 1.4GB.
What I Got Wrong
I architected Kreotar around the belief that "client-side = unlimited scale." Wrong. Browsers are weird, capricious beasts. Each has its own memory ceiling, its own CORS paranoia, its own idea of what "background processing" means.
The Architecture That Survived
┌─────────────────┐
│ UI Layer │ (React, disposable)
├─────────────────┤
│ Worker Pool │ (4-8 Web Workers, spawn/kill aggressively)
├─────────────────┤
│ WASM Bridge │ (Emscripten, -sALLOW_MEMORY_GROWTH=1)
├─────────────────┤
│ Tool Modules │ (Lazy-loaded, 200KB chunks max)
└─────────────────┘
The bridge layer was the surprise hero. I initially compiled each tool as a standalone WASM blob. Tool #87 (HEIC converter) was 14MB. First load: 8 seconds on 4G. Dead on arrival.
Now tools stream. WASM compiles on first use, not page load. Users wait once per tool, not once per visit.
What Actually Failed
FFmpeg in browser: 18MB WASM, 6-minute compile on mobile. Moved to server-side for videos >100MB. I admit defeat.
SharedArrayBuffer: Required for true parallel processing. Still blocked by default in Firefox with certain extensions. Had to build a fallback that uses postMessage and feels 40% slower.
File System Access API: Chrome-only. My "seamless local file editing" feature works for 60% of users. I ship it anyway with a graceful degradation to downloads.
One Thing I'd Do Differently
I spent three weeks optimizing WASM build flags before realizing the bottleneck was DOM thrashing. Profile your actual app, not your benchmarks.
The Genuine Question
Has anyone found a reliable way to detect "this browser tab is about to be killed by the OS" before it happens? I've tried memory pressure APIs, performance.memory, heartbeat workers. All garbage.
Top comments (0)