Offloading Heavy Computation in React with Web Workers and useRef
A 50ms synchronous task drops frames and locks the UI; React's concurrent features don't fix that. Web Workers are the only browser-native way to run expensive computation without jank, and pairing them with useRef and useEffect gives a clean, leak-free integration pattern that works today in any React project.
JavaScript's single-threaded design keeps DOM operations safe but chokes on heavy computation. The Event Loop only reschedules work on the same thread; it cannot prevent a 5-billion-iteration loop from freezing the page. Web Workers solve this by running code in an independent V8 instance with its own memory heap, communicating with the main thread through structured-cloned messages via postMessage.
In a React component, a Worker instance belongs in a useRef, not useState, because its lifecycle changes should not trigger re-renders. The Worker is created inside useEffect after the first paint, and terminated in the cleanup function to prevent memory leaks. The main thread sends task parameters and receives results through onmessage, updating only the result and loading states that actually drive the UI.
This pattern suits any pure computation that exceeds a 50ms frame budget: game physics, local LLM inference, encryption, large dataset processing, and media manipulation. It fails for anything touching the DOM, window, or requiring shared memory without serialization overhead.
The architectural insight is not that Workers exist, but that React's hook model maps cleanly onto their lifecycle: useRef for non-reactive instance holding, useEffect for symmetric setup and teardown, and useState only for the computed results that the UI actually renders.
Many developers reach for setTimeout or requestIdleCallback to break up long tasks, but those still run on the main thread and produce jank. The threshold for a Worker is lower than most assume: any synchronous block exceeding 50ms, not just exotic workloads like LLMs or game engines.
The structured-clone constraint is both a safety feature and a performance ceiling. It prevents shared-memory bugs but makes Workers impractical for tasks that need frequent access to large, mutable state without copying overhead.