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
- 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.
- 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.
- 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.
- 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.
- 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