Skip to main content

Total Blocking Time (TBT).

Performance Core Web Vitals & Page Load

How long the page is frozen and unresponsive — target <= 200ms

What does this check test?

Total Blocking Time measures the total amount of time between First Contentful Paint (FCP) and Time to Interactive (TTI) during which the main thread was blocked long enough to prevent input responsiveness. Any task longer than 50ms is considered a 'long task,' and the blocking time is the duration beyond 50ms. For example, a 70ms task contributes 20ms of blocking time. TBT is the lab-metric proxy for the field-metric Interaction to Next Paint (INP). Google considers TBT 'good' at 200ms or less.

Why does it matter?

When the main thread is blocked, the browser cannot respond to user input — clicks, taps, and key presses are delayed or ignored entirely. Users experience this as the page being 'frozen' or 'janky.' High TBT directly correlates with poor real-world interactivity and user frustration. TBT is heavily weighted in Lighthouse performance scoring (30% of the total score), making it one of the most impactful metrics to optimize for better Lighthouse results.

Who is affected?

Sites with heavy JavaScript frameworks (React, Angular, Vue), large third-party script bundles, complex client-side rendering, or extensive DOM manipulation are most affected by high TBT. Single-page applications that hydrate on the client side are particularly prone to blocking the main thread during initial load.

Where does this apply?

Long tasks occur on the browser's main thread during page load, typically caused by JavaScript parsing and compilation, large component tree hydration, synchronous layout calculations (forced reflows), and complex CSS selector matching. Use Chrome DevTools Performance panel to identify long tasks (shown as red-flagged bars on the Main thread timeline).

How to fix it

Break up long JavaScript tasks using `setTimeout` or `requestIdleCallback` to yield to the main thread. Yield to the main thread between chunks of work:
js
function yieldToMain() {
  return new Promise(resolve => setTimeout(resolve, 0));
}

async function processItems(items) {
  for (const item of items) {
    doWork(item);
    await yieldToMain(); // let browser handle user input
  }
}
Use code splitting to load only the JavaScript needed for the current page:
js
// Route-level code splitting in React
const Dashboard = React.lazy(() => import('./Dashboard'));

function App() {
  return (
    <Suspense fallback={<Spinner />}>
      <Dashboard />
    </Suspense>
  );
}
Defer non-critical JavaScript with `<script defer>` or `<script type="module">`. Remove unused polyfills and dependencies. Consider moving heavy computation to Web Workers. Reduce the DOM size — large DOM trees increase style calculation and layout time.

References

AppVet checks Total Blocking Time (TBT) automatically

Run a free performance scan and get a full report with actionable fixes, including a Fix with AI prompt you can paste into any coding tool.

Run Audit