What Is Total Blocking Time (TBT)? Complete Guide (2026)
What Is Total Blocking Time?
Total Blocking Time (TBT) measures the total amount of time, between First Contentful Paint and Time to Interactive, when the browser's main thread is blocked long enough to delay user input. It's the lab-data answer to the question "the page looks loaded — why does it feel frozen?"
Key Facts (TL;DR)
- Good TBT: ≤ 200 ms — the page feels responsive immediately after it paints.
- Needs Improvement: 200 – 600 ms — users notice clicks "not registering" for a moment after page load.
- Poor: > 600 ms — sluggish, broken-feeling experience; clicks pile up and fire after a delay.
- Highest single weight: TBT accounts for approximately 30% of your overall web performance score — more than any other metric.
- Lab metric, not field: TBT is a synthetic-test metric. The real-user equivalent is Interaction to Next Paint (INP), which Google uses as the actual Core Web Vital ranking signal.
- Long Task = > 50 ms: Any single main-thread task over 50 ms blocks input. TBT sums the "blocking portion" (duration − 50 ms) of every long task during load.
- Business impact: Pinterest cut their JavaScript bundle by ~40% and saw a 15% increase in SEO traffic and 15% lift in sign-up conversions (Pinterest engineering case study, 2017).
Think of the main thread as a single cashier in a shop. The cashier handles every task — ringing up sales, answering questions, restocking shelves. If they spend 600 ms restocking, the next customer in line waits 600 ms. TBT is how long, in total, customers spent waiting.
Why TBT Matters for Engagement and Rankings
- Perceived speed:A page can hit a fast Largest Contentful Paint (LCP) and still feel broken. Users tap a button right after it appears — and nothing happens for 800 ms because JavaScript is parsing in the background. That's TBT making your fast page feel slow.
- Performance score weight: TBT carries ~30% of your overall performance score — the single highest-weighted metric. Cutting TBT in half is usually the highest-leverage change you can make to a sluggish page.
- Field correlation with INP: Pages with high lab TBT almost always show poor real-user INP, which is a Core Web Vital ranking signal. Fixing TBT in the lab fixes INP in the field.
- Conversion impact: Walmart engineering reported that every 100 ms of front-end speed improvement increased conversion by 1%. JavaScript is the byte-for-byte most expensive resource on the web — a small cut goes a long way.
- AI search visibility: Generative search systems preferentially cite pages that already rank well organically. Slow TBT pulls down INP, INP pulls down rankings, and rankings pull down AI citation odds.
How TBT Is Calculated
TBT measures the "blocking portion" of every long task on the main thread between First Contentful Paint and Time to Interactive. The arithmetic is simple, the ergonomics are not.
- Long Task: Any contiguous block of main-thread work that takes more than 50 ms. The 50 ms threshold matches the RAIL model: humans expect input to be acknowledged within ~100 ms, and the browser needs roughly 50 ms of headroom to schedule the response.
- Blocking Time per task:
task_duration − 50 ms. A 70 ms task contributes 20 ms. A 250 ms task contributes 200 ms. A 49 ms task contributes 0. - Total Blocking Time: Sum of blocking times of every long task between FCP and TTI.
Example timeline (between FCP and TTI):
Task A: 80 ms → blocking = 30 ms
Task B: 45 ms → blocking = 0 (under threshold)
Task C: 250 ms → blocking = 200 ms
Task D: 120 ms → blocking = 70 ms
TBT = 30 + 0 + 200 + 70 = 300 ms (needs improvement)Why 50 ms?
Google's RAIL performance model targets a 100 ms response budget for user input. The browser needs roughly 50 ms of that budget for its own work (event dispatch, paint), leaving 50 ms before any in-progress task starts pushing the total over the perceptual threshold. Anything beyond 50 ms is "long" from the user's point of view.
TBT vs INP — Lab vs Field
TBT and Interaction to Next Paint (INP) measure the same fundamental problem — main-thread blocking — but at different times and from different angles.
| Aspect | TBT | INP |
|---|---|---|
| Measurement type | Lab (synthetic test) | Field (real users via CrUX) |
| When measured | Between FCP and TTI (during load) | Across the entire page lifetime (every interaction) |
| What it captures | Total blocking time during load | Worst single user-input response delay (98th percentile) |
| Good threshold | ≤ 200 ms | ≤ 200 ms |
| Used for ranking? | No (informational) | Yes (Core Web Vital since March 2024) |
| Performance score weight | ~30% | Not in score; ranking signal directly |
Bottom line:Optimize TBT in your lab tests because it's easy to measure repeatably during development, and it correlates strongly with the INP your real users will experience. Fixing one fixes the other.
How to Check Your TBT Score
- Greadme's deep scan — surfaces TBT alongside every other Web Vital and pairs each issue with an AI-generated fix or a one-click GitHub PR. Recommended starting point.
- Greadme's crawler scan — measures TBT across every indexable page so you can identify which templates ship the heaviest JavaScript bundles.
- Chrome DevTools → Performance tab — record a load locally; the "Long Tasks" lane highlights every task > 50 ms with the offending function visible in the call tree. The single best tool for tracing which JavaScript is blocking the thread.
- Google Search Console → Core Web Vitals report — TBT itself isn't reported (it's a lab metric), but the related INP field metric is. Use INP issues here as a proxy for TBT problems on those URLs.
- web.dev articles — Google's reference documentation for RAIL, Long Tasks API, and INP migration is a useful primary source.
9 Proven Ways to Reduce TBT
1. Cut JavaScript Bytes Aggressively
Every kilobyte of JavaScript must be downloaded, parsed, compiled, and executed — and every step happens on the main thread. A 1 MB JS bundle on a mid-range Android phone routinely produces 1,000–3,000 ms of TBT.
Fix:Audit your bundle. Remove unused libraries (a moment-formatting library you only use on one page; a charting library imported into a hero section that doesn't render charts). Modern bundlers' tree-shaking only goes so far — manual review pays off.
2. Code-Split by Route and Component
Don't ship the JavaScript for the cart, the dashboard, and the admin panel on the homepage. Code splitting lets each route load only what it actually needs.
// React example: lazy-load below-the-fold widgets
const Comments = React.lazy(() => import('./Comments'));
const VideoEmbed = React.lazy(() => import('./VideoEmbed'));3. Defer or Async Non-Critical Scripts
A blocking <script src="analytics.js"> in the head adds its full execution time to TBT. The same script with defer runs after the document is parsed and at much lower priority.
Fix: Default every third-party script to defer or async. Reserve synchronous loading for code that must run before paint (rare).
4. Break Up Long Tasks With Yielding
A 400 ms task contributes 350 ms to TBT. Two 200 ms tasks contribute 2 × 150 = 300 ms. Four 100 ms tasks contribute 4 × 50 = 200 ms. Same total work, lower TBT — because yielding gives the browser a chance to handle input between chunks.
// Yield between chunks of work
async function processItems(items) {
for (const item of items) {
doExpensiveWork(item);
await new Promise(resolve => setTimeout(resolve, 0));
// Browser can handle clicks/scroll here
}
}For modern browsers, scheduler.yield() is even better — it resumes faster than setTimeout(0) while still letting input through.
5. Move Heavy Work to Web Workers
Web Workers run JavaScript on a separate thread. CPU-bound work — parsing large JSON, image processing, search indexing, encryption — should never run on the main thread.
Fix: If a task takes > 100 ms and doesn't need DOM access, it belongs in a Worker. Libraries like comlink make Workers feel like normal async function calls.
6. Audit and Trim Third-Party Scripts
Tag managers, A/B testing tools, chat widgets, and ad scripts are TBT's favorite hiding place. Each one runs on your main thread. The page owner pays the cost.
Fix: Use a tag manager to consolidate scripts, then enforce a quarterly audit — every third-party tag must justify its TBT cost or be removed. For unavoidable scripts (analytics, consent), load them after page load using requestIdleCallback.
7. Avoid Synchronous JSON.parse on Large Payloads
JSON.parse is synchronous and runs on the main thread. A 2 MB JSON response can produce 200+ ms of blocking time on mobile.
Fix: Parse large payloads in a Web Worker, paginate the API response, or use streaming JSON parsers. For server-rendered apps, hydration data should be tightly scoped.
8. Use Server-Side Rendering or Static Generation
Single-page apps that render entirely client-side push enormous amounts of JavaScript work onto the main thread before the page is interactive. SSR or static generation hands the browser ready-to-paint HTML — JS only needs to attach event handlers.
Fix:If you're using React, Vue, Svelte, etc., move to a framework that supports SSR or static export (Next.js, Nuxt, SvelteKit, Astro). Hydration cost is non-zero but typically 5–10× cheaper than client-side rendering from scratch.
9. Set a JavaScript Performance Budget
The cheapest TBT to fix is the TBT that never shipped. Performance budgets enforce a hard cap on bundle size or main-thread time during build.
Fix:Add a budget rule to your bundler config (e.g. fail the build if the main bundle exceeds 200 KB gzipped). Track TBT in CI and fail PRs that regress it > 50 ms.
Common TBT Problems and Fixes
Problem: Heavy Client-Side Framework on a Content Page
What's happening: A blog post or marketing page is rendering through a full SPA framework with hydration, shipping 500+ KB of JS to display largely static text.
Fix: Move static pages to static generation (SSG) or partial hydration (Astro's "islands" model). Most marketing pages don't need React on the client at all — only interactive widgets do.
Problem: Tag Manager Stack Adds 800 ms TBT
What's happening: Analytics, A/B testing, heatmaps, chat, ads — five+ third-party scripts all loading synchronously on first paint.
Fix: Audit and remove duplicate analytics tools. Consolidate the rest into one tag manager that defers loading. Anything not needed for the first 5 seconds should fire on requestIdleCallback.
Problem: Large Hydration Payload
What's happening: The server sends an 800 KB __NEXT_DATA__ blob containing data the client doesn't actually use, and the framework parses + reconciles all of it on load.
Fix: Trim hydration data to only what the client actually consumes. Move read-only data into the rendered HTML; only ship interactive state through hydration.
Problem: Long-Running useEffect on Mount
What's happening: A React component runs an expensive synchronous calculation in useEffect(() => ..., []), blocking input for hundreds of milliseconds right after the page paints.
Fix: Move the work off the main thread (Web Worker), break it into yielding chunks, or compute it on the server and pass results as props.
FAQ
What is a good TBT score?
Google defines a good TBT as 200 ms or less. Anything between 200 ms and 600 ms "needs improvement," and over 600 ms is poor. The thresholds apply to lab measurements; for the equivalent real-user metric (INP), the same 200 ms threshold applies but is measured at the 98th percentile of interactions.
Is TBT a Core Web Vital?
No. TBT is a lab metric — it's measured during synthetic tests, not from real users. The real-user equivalent is Interaction to Next Paint (INP), which replaced First Input Delay (FID) as the responsiveness Core Web Vital in March 2024. Google uses INP, not TBT, as the ranking signal.
Why is TBT weighted so heavily in performance scoring?
Because main-thread blocking is the most user-visible cause of a sluggish-feeling page. A page can render quickly and still be unusable if every tap and scroll lags. Performance scoring weights TBT at ~30% to reflect that responsiveness matters as much as raw load speed.
What's the difference between TBT and TTI?
Time to Interactive (TTI) marks the moment when the main thread has been quiet for 5 seconds — the page is "done." TBT measures how much blocking happened during the run-up to TTI. A page can have a fast TTI of 4 seconds but a poor TBT of 800 ms if there were several long tasks in the load window.
Will fixing TBT improve INP automatically?
Almost always yes. The same root causes — heavy JavaScript bundles, long-running event handlers, blocking third-party scripts — produce both bad lab TBT and bad real-user INP. The 9 fixes above target both metrics.
Why does my desktop TBT look fine but mobile is terrible?
Mobile CPUs are 4–10× slower than desktop CPUs at JavaScript execution. A 100 ms task on a high-end laptop becomes a 400–1,000 ms task on a mid-range Android phone. Always test TBT under simulated mobile conditions (4× CPU slowdown is the standard) — never trust desktop numbers alone.
Does TBT affect AI search engines like ChatGPT and Perplexity?
Indirectly. AI search systems preferentially cite pages that already rank well in traditional search — and high TBT correlates with poor INP, which is a Core Web Vital ranking signal. Fixing TBT lifts INP, which lifts ranking, which lifts your odds of being chosen as an AI citation.
Conclusion
TBT is the metric behind every "the page looks loaded but my taps don't register" experience. It carries the highest weight in any modern performance score (~30%) and correlates directly with the INP signal Google uses for ranking. The fix is almost always the same: ship less JavaScript, defer what you can, break up what you can't, and move heavy work off the main thread.
The business case is real — Pinterest cut their JavaScript bundle by 40% and saw a 15% increase in both SEO traffic and sign-up conversions. Run a Greadme deep scan to see your TBT, identify which scripts are blocking the main thread, and get a prioritized fix list.
