TBT > 200ms high
Total Blocking Time (TBT) too high
Long main-thread tasks between first paint and interactivity sum past 200ms, making the page feel frozen to early clicks and taps.
What you see
Total Blocking Time — 0.6 s Minimize main-thread work · Reduce JavaScript execution time
What’s actually happening
Lighthouse flags TBT in the red and the page feels sticky right after it appears — you click a button or menu and nothing happens for a beat. The screen has painted, but the main thread is jammed parsing and running JavaScript, so it can't respond to input. TBT carries 30% of the Lighthouse performance score and is the lab stand-in for field INP, so a bad number here usually means bad real-world responsiveness too.
Common causes
- Large first-party JS bundles parsed and executed on load (often an un-code-split SPA)
- Third-party scripts — tag managers, analytics, A/B tools, chat widgets, ad tags — running long tasks on the main thread
- Hydration of a server-rendered framework (Next/Nuxt/etc.) doing a single big synchronous pass
- Expensive work in a long task that never yields, e.g. parsing a huge JSON blob or building a big DOM in one go
- Polyfills and duplicate dependencies shipped to modern browsers that don't need them
How to fix it
- Find the long tasks in a traceOpen Chrome DevTools → Performance, record a load, and look for tasks with the red corner flag (>50ms) in the Main track. The Bottom-Up and Call Tree tabs attribute the time to specific scripts. This tells you what to cut before you start cutting.
- Code-split and defer non-critical JSBreak large bundles with dynamic `import()` and route-based splitting so only above-the-fold code runs at load. Add `defer` (or `type=module`) to scripts, and lazy-load below-the-fold components so their JS doesn't compete during the critical window.
- Tame third-party scriptsLoad analytics/chat/ads after interaction or on idle (requestIdleCallback), or move them into a web worker with Partytown. Audit which tags are actually needed — a single chat widget can add 200ms+ of blocking on its own.
- Break up long tasks so the thread can yieldSplit monolithic work into chunks and yield with `await scheduler.yield()` (or `setTimeout`/`postTask`) so the browser can handle input between chunks. Even keeping each task under 50ms removes it from the TBT total entirely.
- Ship less to modern browsersDrop legacy polyfills via a modern build target, dedupe dependencies, and tree-shake. Less code parsed is the most direct lever on TBT — every KB of JS is parse + compile + execute time on the main thread.
Stop it recurring
Set a JS budget in CI (e.g. Lighthouse CI assertions on TBT and total byte weight) so regressions fail the build instead of reaching production.
Related errors