Total Blocking Time (TBT)
Total Blocking Time (TBT) measures how long the browser’s main thread was too busy to respond to the visitor during page load. The main thread runs JavaScript, calculates styles and lays out the page. While it is busy with a long task, taps, clicks and key presses have to wait.
Any task that runs longer than 50 ms is a long task. TBT adds up the part of each long task beyond those 50 ms, between First Contentful Paint and the point where the page becomes reliably interactive. A single 250 ms task contributes 200 ms of blocking time.
Why it matters
A page can look ready and still ignore the visitor. Buttons do not react, menus do not open, and typing lags. People tap again, or give up. TBT is a lab measurement, but it is the best lab signal for responsiveness problems that real users experience, which show up in the field as poor Interaction to Next Paint. It also carries the largest weight in the Lighthouse performance score.
What we check
We read the TBT value from the Lighthouse mobile run in PageSpeed Insights:
| Result | TBT |
|---|---|
| Good | 200 ms or less |
| Needs attention | over 200 ms, up to 600 ms |
| Poor | over 600 ms |
The mobile test uses a throttled CPU, comparable to a mid-range phone. Scripts that feel instant on a developer laptop can take several times longer there.
How to fix
Almost all blocking time comes from JavaScript. The fixes come down to running less of it, later, in smaller pieces.
Ship less JavaScript. Audit your bundles and remove unused libraries and polyfills for browsers you no longer support. Split code per route so each page loads only what it needs. The “Reduce unused JavaScript” item in the Lighthouse opportunities list names the worst files.
Defer what is not needed for the first screen. Scripts with defer run after the HTML is parsed
and do not block rendering. Load non-essential widgets after the page is idle or after the first
interaction:
<script src="/app.js" defer></script>
// Load the chat widget only when the visitor shows interest.
button.addEventListener('click', () => import('./chat-widget.js'), { once: true });
Review third-party scripts. Tag managers, analytics, chat, heatmaps, A/B testing, social embeds and ad scripts often account for most of the blocking time. For each one, ask whether it is still needed, whether it can load later, and whether a lighter alternative exists. The Lighthouse report groups blocking time by third party.
Break up long tasks. If your own code must do a lot of work, split it into chunks and give the browser a chance to handle input between them:
async function processAll(items) {
for (const item of items) {
process(item);
// Yield to the main thread between items.
await new Promise((resolve) => setTimeout(resolve, 0));
}
}
In browsers that support it, scheduler.yield() does the same with better scheduling. Feature-detect
it and fall back to setTimeout.
Reduce hydration cost. Frameworks that render on the server and then “hydrate” the whole page in the browser can create one very long task. Hydrate only the interactive parts (islands, partial or lazy hydration), or render static content without client-side JavaScript at all.
Move heavy work off the main thread. Data processing, parsing and similar work can run in a Web Worker, which does not block input.
Finding the culprit
In Chrome DevTools, record a page load in the Performance panel with CPU throttling enabled. Long tasks are marked with a red corner. Click one to see which scripts and functions it spent its time in. The Lighthouse diagnostics “Minimize main-thread work” and “Reduce JavaScript execution time” point to the same scripts.