sfw/fix
Minimize main-thread work high

Minimize main-thread work

The browser's main thread spends too long parsing, compiling, and running JavaScript plus doing layout and paint.

What you see

Minimize main-thread work — 4.8 s
Consider reducing the time spent parsing, compiling and executing JS. You may find delivering smaller JS payloads helps with this.

What’s actually happening

The page looks loaded but doesn't respond — taps and clicks lag, scrolling stutters, and the input cursor freezes for a beat. This shows up alongside a bad Total Blocking Time and a sluggish Interaction to Next Paint in field data. On a mid-tier Android phone it's far worse than on your dev machine, because Lighthouse throttles CPU 4x by default.

Common causes

  • A large JavaScript bundle that has to be parsed and compiled before anything runs — Script Evaluation and Script Parse & Compile dominate the breakdown table.
  • Hydration of a heavy SPA (React/Vue/Angular) that rebuilds the whole DOM on load, spiking Script Evaluation.
  • Expensive layout/reflow from JS reading and writing geometry in a loop (layout thrashing), inflating the Style & Layout row.
  • Long tasks over 50ms that never yield — one big synchronous function blocks everything behind it.
  • Third-party tags executing on the main thread during startup (see related entry).

How to fix it

  1. Read the breakdown table firstLighthouse groups the time into Script Evaluation, Style & Layout, Script Parse & Compile, Rendering, Parse HTML & CSS, and Garbage Collection. Whichever row is biggest tells you where to aim. Script Evaluation high means too much JS; Style & Layout high means reflow problems.
  2. Code-split and deferSplit the bundle by route with dynamic import() so each page only ships what it needs. Move non-critical scripts behind defer or load them after the load event. Webpack/Vite/Rollup all support route-level splitting out of the box.
  3. Break up long tasksFind tasks >50ms in the Performance panel's flame chart, then chunk the work with await scheduler.yield() (or setTimeout/requestIdleCallback as a fallback) so the thread can handle input between chunks.
  4. Move heavy compute to a Web WorkerParsing large JSON, image processing, diffing, crypto — anything CPU-bound that doesn't touch the DOM belongs in a worker. Comlink makes the postMessage boundary feel like a normal function call.
  5. Stop layout thrashingBatch DOM reads then writes. Reading offsetWidth/getBoundingClientRect after a write forces a synchronous reflow; do all reads first, then all writes. fastdom or requestAnimationFrame batching enforces this.

Stop it recurring

Add a bundle-size budget and a Lighthouse CI assertion on total-blocking-time so a regression fails the build instead of reaching production.

Related errors