fastmon Docs
Web Vitals

Time to First Byte (TTFB)

How long the server took to send the first byte, and why it's the floor every other metric sits on.

TTFB measures the time from navigation start to the first byte of response landing in the browser. It's the floor every other metric sits on: nothing visual can happen before the first byte arrives.

TTFB is not a Core Web Vital. A diagnostic metric, but the single most important one when you're trying to take apart a slow LCP or FCP.

What's a good TTFB?

TTFB is reported in milliseconds. Track the 75th percentile.

TTFB at p75Rating
≤ 800 msGood
≤ 1,800 msNeeds improvement
> 1,800 msPoor

The thresholds already bake in the realistic costs of cold-cache visitors on typical mobile networks. TTFB under 200 ms (warm cache, close PoP) is excellent. Over 1 s feels to the visitor like a page that just won't get going.

What goes into TTFB

TTFB is the sum of:

  1. Redirects. Each 301/302 is a separate roundtrip.
  2. DNS lookup. First request to your origin per session.
  3. TCP + TLS handshake. One roundtrip each on a fresh connection (HTTP/2 reuses connections; HTTP/3 collapses them further).
  4. Request → server processing → response. The time the server spends thinking before it can start streaming.
  5. First byte arrival. Network propagation from server to client.

If your TTFB is dominated by step 4, the fix is in your application. If it's dominated by 1–3 or 5, the fix is in your network and edge posture.

When TTFB is reported

TTFB is captured from the Navigation Timing API as responseStart - fetchStart. The tracker includes it with every page-view that has a new navigation entry.

TTFB behind 103 Early Hints

On sites behind 103 Early Hints (Shopify storefronts, Cloudflare) TTFB regularly reads below the server's own Server-Timing phases for the same pageview. Both are right. responseStart marks the first byte of the first response, and with Early Hints that is the interim 103 the edge answers in about one round trip; your server's real response, and the Server-Timing header on it, comes later. The two measure different responses of the same request.

To tell them apart, fastmon also stores ttfb_final: the browser's finalResponseHeadersStart, the first byte of the final response. The tracker reads it with the same activation adjustment and 60-second cap as TTFB (wire key frs), and the collector drops a value below ttfb. Reading the pair:

ttfb_finalMeaning
empty (NULL)The browser does not report it (Safari, Firefox; Chromium before 115), or the pageview was a soft navigation or a bfcache restore.
equal to ttfbNo interim response; the first byte was the real one.
above ttfbEarly Hints active. The difference is the edge's head start, and ttfb_final is the value the server's edge_dur + backend_dur has to fit into.

TTFB itself stays responseStart, so it keeps comparing to CrUX and the web-vitals field data. ttfb_final is a row field on POST /analytics/beacons, part of the visitor pageview detail, and an aggregatable metric (avg, p50 to p99).

In the dashboard, the TTFB page shows an Early Hints panel wherever the two values differ: the head start at the selected percentile, the two first bytes as one split bar, and the share of pageviews the final one was measured on (a Chromium-only timing, and soft navigations carry none). The response breakdown draws the head start as its own "Early Hints" row inside the wait, ends the server phases on it, and starts the transfer bar at the final response headers rather than at the first byte. In the Explorer, ttfb_final is a chartable metric, a sortable column, and a drawer field; the pageview drawer shows it only when it differs from TTFB.

How to see TTFB in fastmon

The dashboard's Performance section shows TTFB at p50, p75, p95.

  • By country: group by country. RTT to your origin is the dominant factor for cold connections, and it's lopsided by region. If your origin is in Frankfurt, TTFB to São Paulo will be bad regardless of your server.
  • By URL: group by URL. If a single page has a much worse TTFB, the application is doing per-page work (DB query, third-party fetch) that the others aren't.
  • By release: compare_to_release_id. A backend deploy that added a synchronous external call usually shows up here first.

How to improve a slow TTFB

  1. Cache HTML at the edge. A static or near-static page should be served from a CDN cache, not your origin. This is the highest- leverage change you can make.
  2. Move closer to your visitors. Multi-region origins or a well-distributed edge layer cut RTT for everyone outside your primary region.
  3. Profile the server response. Use APM, server logs, or a server-timing header to find what's slow. Common culprits: a slow database query, a synchronous third-party call, or server-rendered React hydration that traverses too much data.
  4. Reduce redirect chains. Every redirect adds a full roundtrip. http://https://www. → final URL is three roundtrips before the browser even gets the HTML.
  5. Use HTTP/2 or HTTP/3. Most modern stacks already do, but double-check; older origins still negotiate HTTP/1.1.

Common surprises

  • A fast TTFB on Vercel/Netlify can hide a slow origin. The edge caches the response; cache misses might be 10× slower. Check your cache hit rate alongside TTFB.
  • TTFB doesn't include client-side render time. A page with a fast TTFB and a slow LCP is shipping a lot of work to the browser, not the server. SSR doesn't help if hydration is the bottleneck.
  • Service-worker caches affect TTFB the visitor experiences. If your service worker serves the navigation, TTFB can be near zero on repeat visits.

What's next

On this page