All posts
PerformanceTBTJavaScript

What is TBT (Total Blocking Time)?

TBT measures how long the main thread is blocked and unable to respond to user input. Learn what causes high TBT and how to reduce it to improve page responsiveness.

debugutils.dev

Total Blocking Time (TBT) measures the total duration during which the browser’s main thread is blocked long enough to prevent input response between First Contentful Paint and Time to Interactive. In plain English: how long does JavaScript keep the page frozen while it loads?

TBT is a lab-only metric used as a proxy for INP (Interaction to Next Paint), which is the field-data equivalent and the actual Core Web Vital. A high TBT almost always means a high INP in the real world.

How TBT Is Calculated

The browser’s main thread runs JavaScript, handles user events, and manages rendering. When a task runs for more than 50ms, it’s considered a “long task.” The blocking time of each long task is the portion beyond 50ms.

For example, a 200ms task has a blocking time of 150ms. TBT is the sum of blocking times for all long tasks between FCP and TTI.

Task duration: 200ms
Blocking time:  150ms (200ms - 50ms)

TBT Thresholds

ScoreTime
GoodUnder 200ms
Needs improvement200ms – 600ms
PoorOver 600ms

These thresholds apply to Lighthouse test conditions (throttled mobile CPU and network).

What Causes High TBT?

Large JavaScript bundles Parsing and executing large JS files blocks the main thread. A 500KB JavaScript bundle on a mid-range mobile device can block the main thread for several seconds.

Synchronous third-party scripts Analytics, chat widgets, ad scripts, and marketing tools loaded synchronously execute on the main thread. A single poorly written third-party script can add hundreds of milliseconds to TBT.

Unoptimized JavaScript execution JavaScript that does expensive computations during page load — sorting large arrays, heavy DOM manipulation, complex regular expressions — creates long tasks.

React/Vue/Angular hydration Client-side framework hydration executes JavaScript to attach event listeners to server-rendered HTML. For large pages, this can create significant TBT.

Unused JavaScript Code that’s downloaded but never executed still gets parsed. Tree shaking and code splitting reduce this overhead.

How to Reduce TBT

Split large JavaScript bundles Use dynamic import() to load code only when it’s needed. Route-based code splitting means each page only loads the JavaScript it actually uses.

// Instead of importing everything upfront:
import HeavyComponent from './HeavyComponent';

// Load it only when needed:
const HeavyComponent = await import('./HeavyComponent');

Defer non-critical scripts

<script src="analytics.js" defer></script>
<script src="chat-widget.js" async></script>

defer executes after HTML parsing. async downloads in parallel but executes as soon as it’s ready.

Load third-party scripts with a facade For things like YouTube embeds, chat widgets, and social buttons — use a lightweight placeholder (facade) that only loads the real script when the user interacts with it.

Use a web worker for heavy computations Move expensive JavaScript off the main thread into a Web Worker:

const worker = new Worker('heavy-task.js');
worker.postMessage({ data: largeArray });
worker.onmessage = (e) => updateUI(e.data);

Reduce JavaScript payload overall Audit your bundle for unused dependencies (npm ls, bundle analyzers). Remove polyfills for modern browsers. Consider lighter alternatives to heavy libraries.

Measuring TBT

Run a free test on debugutils.dev. Your Lighthouse report includes TBT along with other performance metrics. The detailed diagnostics show which scripts are responsible and give estimated time savings for specific optimizations.

TBT is one of the strongest predictors of real-world interactivity issues, so it’s worth optimizing even if it’s not directly in Google’s ranking algorithm.