Total blocking time: how to find your long tasks and what to fix

Total blocking time is the milliseconds after the first paint when the main thread was held by long tasks. This chapter shows how to find yours, the 200 ms Lighthouse target, and the five fixes that move it.

Implementation7 min read2908 views
Total blocking time: how to find your long tasks and what to fix

Total blocking time (TBT) is the amount of time after the first paint when the main thread was busy with long tasks — any task that ran for more than 50 milliseconds without giving the browser a chance to respond. It is the lab number that stands in for how quickly a page reacts to a tap or a keypress. Lighthouse marks 200 milliseconds or less as good on a throttled mobile run, and most sites that feel heavy fail that line by an order of magnitude.

Read this first

You need to be able to run Lighthouse, either in Chrome DevTools or as a command-line tool, and to edit the scripts your page loads. No build step is required. This chapter assumes you already understand that a task is a chunk of JavaScript the browser runs to completion before it can handle input; interaction to next paint covers what the user feels, and this chapter covers the number a tool reports. The measured companion to this chapter is total blocking time on 24 homepages, where we ran the same audit across a fixed set of sites.

Why total blocking time happens at all

A browser runs JavaScript on one main thread, and it cannot start the next piece of work — including painting, scrolling or reacting to a click — until the current task finishes. A task is counted as blocking once it passes 50 milliseconds; the blocking time is the part above that 50 milliseconds, added up across the page load. So TBT is not a measure of how much script you ship. It is a measure of how long your longest single pieces of script run without yielding.

That distinction decides where to look. A site can load a megabyte of JavaScript and still score well if the work is split into short tasks, and a site can ship a small bundle and score badly if one function runs for a second. The table below is the split between what makes a task long and what does not.

What the browser runsCounts toward TBT?Why
A task under 50 msNoIt never crosses the blocking threshold
A 300 ms task250 msOnly the part past 50 ms is counted
Ten 40 ms tasks back to backNoEach one yields; none is a long task
Third-party tag that runs 400 ms350 msThird-party code blocks the same thread as your own
Parsing HTML and CSSNoBlocking time is script on the main thread, not stylesheet parsing
Total blocking time is not how much script you load. It is how long your worst single task runs before it lets go of the thread.

Two consequences follow from that. First, the highest-value fix is usually to find one or two long tasks, not to trim bytes everywhere. Second, TBT is a lab metric measured on one machine under one throttling profile; it is a proxy for responsiveness, not the responsiveness a real visitor experiences, which is what interaction to next paint records in the field.

Do it in this order

Five steps, each with a check you can run before moving on. The first is free and tells you whether the rest are worth doing.

  1. Measure before you touch anything. Run the page through PageSpeed Insights or the Lighthouse CLI and record the TBT value, plus the two audits that explain it: "Minimize main thread work" and "Reduce unused JavaScript". Done when you have a number and the longest scripts named, not a feeling that the page is slow.
  2. Separate third-party from first-party cost. In the Lighthouse report, open the main-thread breakdown and note how much script time is attributed to domains you do not control — tag managers, chat widgets, A/B tools, ad pixels. Done when you can say what share of the blocking time belongs to code you did not write.
  3. Cut or defer the third-party scripts. Load analytics and chat only after the first interaction, or drop the ones whose value you cannot state. A single synchronous tag in the head can add hundreds of milliseconds of blocking time on its own. Done when the third-party share in step two has dropped.
  4. Break up your own long tasks. Move work that does not need to run before the first paint into an idle callback, split a heavy loop into chunks that yield between iterations, and defer hydration of components below the fold. Done when the longest task in the report is under 50 milliseconds.
  5. Re-measure and keep both numbers. Re-run the same audit on the same throttling profile and compare against step one. Done when you can state what changed and by how much, and you have stored both readings somewhere you can find them.

The deliverable: a long-task triage table

The table below is the one we use to turn a Lighthouse report into one action. Match the longest task in your own report to a row, then take that row's fix. If two rows match, do the third-party row first — it is usually the cheaper change.

Longest task isLikely causeFirst fix
A tag manager or analyticsThird-party code in the headDefer it past first interaction, or remove it
Your framework's hydrationClient-side rendering of static contentRender that content in HTML, hydrate in pieces
One big data transformA loop that never yieldsChunk the work, yield between batches
A chat or support widgetScript that initialises on loadLoad on click, not on page load
Unknown, deep in a vendor bundleUnused dependency reached at runtimeFind it in the coverage panel, then drop it

If you want the raw number without opening DevTools, the command below runs Lighthouse headless and prints just the TBT value and the two explaining audits as JSON. It needs Node installed; it does not need an account.

npx --yes lighthouse "$URL" --only-categories=performance \
  --preset=desktop --output=json --output-path=/tmp/lh.json --quiet \
  && node -e 'const a=require("/tmp/lh.json").audits;
     console.log("TBT ms:", a["total-blocking-time"].numericValue);
     console.log("main-thread ms:", a["mainthread-work-breakdown"].numericValue);
     console.log("unused JS ms:", (a["unused-javascript"].details||{}).overallSavingsMs);'

What goes wrong

Three failures are common, and none of them throws an error, which is why they survive for years.

  1. Optimising for the score instead of the task. The Lighthouse score is a weighted roll-up, and TBT is one input. A change that moves the score by a few points while the longest task stays at 400 milliseconds has not helped a real interaction. Track the task length, not only the score.
  2. Deferring a script that the first screen depends on. If the header or the hero is rendered by script, deferring that script moves the paint later. Move that rendering into the HTML instead of pushing it back.
  3. Trusting a single run. Lab runs on a shared machine vary. A difference under roughly 100 milliseconds between two runs can be noise. Change one thing, re-run twice, and only believe a delta that repeats.

Common questions

What is a good total blocking time?

Lighthouse marks 200 milliseconds or less as good and 600 milliseconds or more as poor, on a throttled mobile profile. Treat 200 ms as the line to clear rather than the goal: a page that clears it and still blocks 180 milliseconds on every tap will feel sluggish, so keep cutting the longest task.

Is total blocking time a ranking factor?

Not by itself. It is not one of the three Core Web Vitals, and no Google document assigns it a ranking weight. It matters because it is the lab proxy for a metric that is official, and because the work that lowers it — fewer third-party scripts, smaller tasks — usually improves real page experience at the same time.

Why is my total blocking time high when the page loads quickly?

Because loading and responding are measured from different points. A page can paint its first content fast and then run a long script that freezes the thread, which is exactly what TBT catches. Check the main-thread breakdown before you touch images or fonts: the fix is almost always in the script, not the assets.

Does total blocking time replace interaction to next paint?

No. TBT is measured in a lab; interaction to next paint is collected from real visits, and a passing lab run does not guarantee a passing field metric. Use TBT to find the long tasks quickly, then confirm the effect in the field data once the change is live.

The boundary worth saying plainly: this chapter is about shortening the tasks that block the thread, not about making the page smaller or faster to download, which are different problems. We have not measured how any single change moves TBT on your site, because it depends on your scripts and your hardware; on a fast machine with no throttling, the same page can show a TBT near zero and tell you nothing. Where this number stops being useful is when the main thread is idle but the page is still slow — at that point the bottleneck is somewhere other than script execution, and a different measurement is the right one. Lining this work up against how QueryWin tracks page performance is reasonable, but re-run step one first.

Part of the QueryWin handbook · Level 2

Total blocking time: how to find your long tasks and what to fix