TBT is a lab metric, not a field metric. It is measured by Lighthouse in a controlled environment using a simulated mobile device (Moto G Power CPU throttled to 4x) on a simulated 4G connection. This matters because TBT can differ significantly from what real users experience — a page with a 600ms TBT in Lighthouse may have much lower TBT on a desktop in a fast network. The real-world equivalent of TBT is INP (Interaction to Next Paint), which measures actual user interaction latency from field data in Chrome UX Report.
Despite being a lab metric, TBT is the strongest proxy for interactivity that Lighthouse can measure without real user interaction. It accounts for 30% of the Lighthouse Performance score. Improving TBT is almost always the same work as improving INP — both are caused by long JavaScript tasks on the main thread.
Key Takeaways
- Good TBT: under 200ms · Needs Improvement: 200–600ms · Poor: over 600ms (Lighthouse thresholds)
- TBT measures the sum of the blocking portion of every long task between FCP and TTI — not just one task
- A "long task" is any synchronous JavaScript execution that takes more than 50ms. The blocking portion is the time over 50ms
- TBT accounts for 30% of the Lighthouse Performance score — the highest weight of any single metric
- Real-world equivalent of TBT is INP (Interaction to Next Paint) in Core Web Vitals field data
- The primary fix is code-splitting, deferring non-critical JavaScript, and moving heavy work off the main thread
How TBT Is Calculated
TBT is the sum of the blocking portions of all long tasks that occur between First Contentful Paint (FCP) and Time to Interactive (TTI).
A long task is any synchronous execution on the main thread that takes more than 50ms. The blocking portion of a long task is everything over 50ms. For a 200ms task, the blocking portion is 150ms. The first 50ms is considered the baseline processing time the browser always needs; beyond that, the main thread is "blocked" from responding to input.
TBT = sum of (task duration − 50ms) for every task longer than 50ms, measured between FCP and TTI.
Example: if three long tasks run between FCP and TTI — one for 300ms, one for 150ms, one for 80ms — TBT = (300−50) + (150−50) + (80−50) = 250 + 100 + 30 = 380ms. This falls in the "Needs Improvement" range.
TBT Thresholds
| TBT | Rating | Lighthouse score impact |
|---|---|---|
| 0–200ms | Good ✓ | Full points (score ≥ 90) |
| 200–600ms | Needs Improvement | Partial points |
| >600ms | Poor | Zero points + significant score penalty |
On a mobile device, TBT of 300ms is common on JavaScript-heavy sites. TBT over 1000ms is frequent on pages that load large analytics bundles, tag manager scripts, or unoptimised React/Angular applications without code-splitting.
TBT vs Other Performance Metrics
| Metric | What it measures | Lab or Field | Lighthouse weight |
|---|---|---|---|
| TBT | Main thread blocking between FCP and TTI | Lab (Lighthouse) | 30% |
| INP | Real user interaction latency (98th percentile) | Field (CrUX) | Core Web Vital |
| TTI | Time until main thread is consistently quiet for 5s | Lab (Lighthouse) | 10% |
| FID (retired) | First input delay only | Field (CrUX, retired Mar 2024) | — |
TBT and INP are measuring similar problems through different lenses. TBT counts blocked time during load. INP counts interaction delay throughout the full page session. The same JavaScript that causes high TBT (large bundles executing synchronously during load) often causes high INP when that same code fires again on user interaction. Fixing one generally improves the other.
TTI and TBT are related but different. TTI measures when the main thread goes quiet — it is sensitive to single large long tasks that run late in the loading phase. TBT captures all blocking time cumulatively. A page with many medium-length tasks may have a good TTI but a poor TBT.
What Causes High TBT
Large Unoptimised JavaScript Bundles
Shipping a single large JavaScript bundle that parses and executes all at once is the most common cause of high TBT. A 500KB minified JS bundle executing on a throttled mobile CPU can take 800–1200ms to parse and evaluate — almost entirely as a single long task. The browser is blocked for the entire execution duration.
Modern frameworks (React, Vue, Angular) with server-side rendering and no code-splitting commonly ship 300–800KB of JavaScript to the client. Without route-based code-splitting, the entire application bundle executes even when the user only needs a fraction of it for the current page.
Third-Party Scripts Executing on Load
Tag managers, analytics libraries, A/B testing scripts, and affiliate trackers often execute large amounts of JavaScript synchronously during page load. Google Tag Manager itself, with a typical tag configuration, contributes 100–400ms of TBT on mobile. When combined with Google Analytics 4, HotJar, an affiliate tracker, and a chat widget, the cumulative third-party TBT can exceed 1000ms on its own.
Third-party scripts are particularly problematic because they are loaded from external origins and cannot be optimised directly — you can only control how and when you load them. Loading them async or defer helps with FCP but does not reduce TBT if they still execute during the FCP→TTI window.
Render-Blocking Script Evaluation
Scripts in the <head> without async or defer block HTML parsing and delay FCP. They also execute synchronously before the main content is visible, contributing to TBT even before the user sees anything. Any <script src="..."> without async or defer in the head is a TBT risk.
Heavy DOM Manipulation on Load
JavaScript that queries and modifies large sections of the DOM on DOMContentLoaded — for example, initialising a component library, hydrating server-rendered HTML, or processing large JSON datasets — creates long tasks. DOM operations themselves are fast, but large batches of them (hundreds of DOM insertions, hundreds of style reads) compound into tasks that exceed the 50ms threshold.
How to Diagnose TBT
Lighthouse Report
Run Lighthouse in Chrome DevTools (Ctrl+Shift+I → Lighthouse tab → Generate report) in Incognito mode to exclude extension interference. The "Performance" section shows TBT as a metric. Below the metrics, "Avoid long main-thread tasks" and "Reduce JavaScript execution time" list the specific long tasks contributing to TBT, sorted by duration, with script origin and function name where available.
Chrome DevTools Performance Panel
Record a Performance trace while the page loads. The "Main" thread lane shows all tasks. Red-flagged tasks at the top of the lane are tasks over 50ms — Lighthouse counts all of these between FCP and TTI as TBT contributors. Click any red task to see the call stack: this identifies the specific function and file responsible.
The "Bottom-Up" tab in Performance sorts all functions by total self-time. Functions at the top are the heaviest contributors to long tasks and the best targets for optimisation.
Page Speed Inspector
The Page Speed Inspector surfaces render-blocking scripts, third-party script count, and inline JavaScript bytes — the three variables most strongly correlated with high TBT scores. A high third-party script count with render-blocking resources is a reliable TBT risk signal without running a full Lighthouse audit.
Fixes for High TBT
Code-Split Your JavaScript Bundles
Route-based code splitting ensures that only the JavaScript needed for the current page is loaded and executed. In a React app with React Router, dynamic import() with React.lazy() splits each route into a separate chunk. A user visiting the homepage never downloads or executes the code for the account settings page. Bundle size per page drops from 400KB to 60–120KB — a proportional TBT reduction.
For non-framework sites, split component libraries and large dependencies by entry point. If a page doesn't use a chart library, don't include it in that page's bundle. Use <script type="module"> with native ES module imports if your target browsers support it — module scripts are deferred by default and evaluated after HTML parsing.
Defer Non-Critical Third-Party Scripts
Load analytics and tracking scripts after the page's TTI window. The pattern for Google Tag Manager: inject the GTM snippet in a requestIdleCallback call fired after window.onload. Real user data will be lost during the initial load window, but for most sites the trade-off improves TBT significantly.
An alternative pattern is to load the GTM script after a small delay (e.g. 2 seconds) using setTimeout. This is less precise than requestIdleCallback but more broadly compatible. The TBT impact disappears entirely because the scripts now execute after TTI, outside the measurement window.
Break Long Tasks into Smaller Chunks
When you have a necessary computation that runs long — data processing, DOM initialisation, search index building — break it into smaller chunks that yield control back to the browser between each chunk.
Use scheduler.postTask() with background priority for non-urgent work. Use setTimeout(fn, 0) to yield between chunks of a synchronous loop. The technique: process 50 items, yield, process 50 more, yield. Each chunk must complete in under 50ms to avoid contributing to TBT.
Move Heavy Work to Web Workers
Web Workers run JavaScript on a background thread, completely separate from the main thread. JSON parsing, search indexing, data transformation, and cryptographic operations are excellent candidates. Work that moves to a Worker cannot contribute to TBT because TBT only measures the main thread.
Comlink (by the Chrome team) provides a clean proxy API for communicating with Workers without the MessageChannel boilerplate. For Next.js, the @builder.io/partytown library automatically moves third-party scripts to Workers.
Audit and Trim Third-Party Scripts
The highest TBT return for minimal development effort is often removing or deferring third-party scripts. Audit every script loaded on the page: what does it do, is it necessary on this specific page, and can it load after TTI? Chat widgets only needed on the support page should not load on the homepage. A/B testing scripts that are no longer running active experiments should be removed entirely.
Find Render-Blocking Scripts and Third-Party Load on Any Page
The Page Speed Inspector shows every render-blocking resource, third-party script origin, and inline JavaScript byte count in seconds — without running a full Lighthouse audit. Use it to identify the TBT contributors before diving into DevTools.
Try the Page Speed Inspector Free →TBT and Lighthouse Score
TBT's 30% weight in the Lighthouse Performance score means that reducing TBT from 600ms to 200ms (from Poor to Good) adds roughly 15–25 points to your overall Performance score, depending on other metrics. No single other metric has this much impact — LCP is 25%, CLS is 15%, FCP is 10%, TTI is 10%, Speed Index is 10%.
A page targeting a 90+ Lighthouse score almost always requires TBT under 200ms. Pages stuck at 65–75 despite good LCP and CLS almost always have TBT in the 400–800ms range as the primary drag.
TBT in the Context of a Full CWV Audit
TBT is not a Core Web Vitals field metric — Google does not use Lighthouse TBT directly as a ranking signal. Google uses INP (field), LCP (field), and CLS (field) as the three Core Web Vitals. However, TBT is the best lab proxy for INP, and improving TBT consistently improves real-world INP scores. A site with TBT under 200ms in Lighthouse rarely fails INP in field data.
Run the Page Speed Inspector to check server-side speed (TTFB, compression, cache), then run Lighthouse locally to check TBT. The two tools together give you the full performance picture: server performance and client-side JavaScript performance.
Affiliate disclosure: Some links on this page may be affiliate links. We only mention tools we've personally used and have an honest opinion about. Affiliate revenue helps keep ByteWaveNetwork's tools free and maintained. We are not paid by any of the tools compared in this article for favorable coverage.