Skip to content
Issue docs

Total Blocking Time in the poor range

Standardpoor_tbtIssue 178

What is this issue?

Total Blocking Time (TBT) adds up how long the browser's main thread was too busy to respond to input while the page loaded. Every task longer than 50 ms counts for the part over 50 ms; a 250 ms script contributes 200 ms of blocking.

Lighthouse scores it in three bands on mobile:

TBT Verdict
200 ms or less Good
200 to 600 ms Needs improvement
more than 600 ms Poor

This check reports pages in the poor band. TBT is measured in the PageSpeed lab run only -- there is no real-user TBT -- and it is the lab stand-in for Interaction to Next Paint.

Why it matters

  • It predicts slow responses to taps. A main thread blocked during load answers the first tap or keypress late, which is what Interaction to Next Paint -- a Core Web Vital -- measures for real users.
  • It is the biggest weight in the Lighthouse score. TBT carries 30% of the performance score PageSpeed Insights shows, more than any other metric.
  • It catches the problem before your users do. INP needs real traffic to report; TBT shows the same cause on a page nobody has interacted with yet.

Fixing it removes a standard-severity finding from the site's health score.

How to fix it

  1. Find the long tasks. The Performance panel in Chrome DevTools, or the "Avoid long main-thread tasks" list in PageSpeed Insights, names the scripts responsible.

  2. Ship less JavaScript. Remove unused code and dependencies, and split bundles so each page loads only what it runs (see "Unused JavaScript").

  3. Defer what is not needed to load. Load chat widgets, analytics and below-the-fold components after the page is interactive, or on first interaction.

  4. Break up long tasks. Yield to the main thread between chunks of work (scheduler.yield(), setTimeout) so input can be handled in between.

  5. Audit third-party tags. Tag managers and ad scripts are a common source of blocking time (see "Heavy third-party code").

Examples

1. A single large bundle

A marketing page loaded one 900 KB JavaScript bundle containing every component on the site, parsed and executed before the page became responsive.

Fails -- TBT 1,140 ms

<script src="/js/bundle.js"></script>

Passes -- TBT 180 ms

<script type="module" src="/js/home.js"></script>
<!-- other components imported on demand -->

2. A chat widget loaded up front

Fails -- TBT 720 ms: the widget's script ran three long tasks during load.

Passes -- TBT 210 ms: the widget loads when the visitor clicks "Chat".

How PixyScan detects this

  1. Chooses which pages to measure. After the crawl, PixyScan measures the homepage plus the pages your own site links to most, up to a per-scan budget (ten pages by default, at most fifty). Measuring every page would take hours and exhaust the PageSpeed Insights quota of the API key it runs under.

  2. Asks the PageSpeed Insights API about each of them, on mobile. This needs the Hobby plan or above and a PageSpeed Insights API key on the site or its workspace.

  3. Reads Total Blocking Time from the Lighthouse lab run (total-blocking-time). There is no field reading to prefer: TBT only exists in a synthetic run.

  4. Raises the issue only above 600 ms -- the bound Lighthouse's mobile scoring calls poor. A page at exactly 600 ms raises nothing.

  5. Raises nothing on a page it did not measure. A page outside the sample, and a page whose measurement failed, are both reported as Not measured. Neither is treated as passing, and neither can fail this check.

What we store

Storage Level

Page Level


Database Table / Prisma Model

PagePerformance (page_performance), with the finding itself on audit_issues.details.


Fields Used

Field Type Description
strategy String mobile or desktop. Every number in the row belongs to one form factor.
fetchedAt DateTime When PageSpeed answered. "Measured three weeks ago" is not "not measured".
errorReason String? Why a sampled page still has no numbers. Null when the call succeeded.
labTbtMs Int? Total Blocking Time from the PageSpeed lab run (total-blocking-time), in milliseconds.

On the finding (audit_issues.details): message, plus metric (tbt), source (lab), valueMs and thresholdMs (600).


Detection Dependencies

  • PageSpeed Insights API v5 (lighthouseResult.audits), mobile strategy
  • The PageSpeed plan feature (Hobby and above) and a PageSpeed Insights API key
  • The crawl's internal link graph, which decides which pages are sampled

Note

A page with no row was never measured. The pass samples the homepage plus the most-linked pages within a per-scan budget (default 10, at most 50), so most pages of a site have no page_performance row at all. That is the normal state, it is drawn as Not measured, and this check cannot be raised against it.

A row whose metric columns are all null with an errorReason set is a different state again: the page WAS sampled and the measurement failed. It is still never reported as passing.

Further reading