Keep Long Tasks Off the Main Thread
MUST: Move expensive computation off the main thread — anything past ~50ms is a long task by Core Web Vitals and eats INP; an 800ms sync loop paints no frames and queues every click, while the CSS spinner keeps turning on the compositor and disguises the freeze as progress. Post it to a Web Worker, or chunk it and yield with `scheduler.yield()` if it must touch the DOM.
Move long-running computation into a Web Worker so the page stays interactive
const worker = new Worker(new URL("./crunch.worker.ts", import.meta.url), { type: "module" });
worker.postMessage(rows);
worker.onmessage = (e) => setResult(e.data);Bad
Good
Why it matters
The main thread renders every frame, runs every event handler, and executes your JavaScript — one queue, one worker. A synchronous computation that runs for 800ms does not make the page slow, it makes it dead: no frames paint, clicks and keystrokes sit in the queue, and a CSS spinner is the only thing still moving (because it is on the compositor) which makes the freeze look like a working loading state.
Anything past roughly 50ms is already a "long task" by Core Web Vitals' definition and eats directly into INP. Post the work to a Web Worker — it runs on its own thread and messages the result back — or, if it genuinely must touch the DOM, chunk it and yield with scheduler.yield() between slices.