HTTP Request Lifecycle: URL bar to painted pixels.

A modern API request hits DNS, TCP, TLS, ALPN, HTTP/2, an edge, an origin, a database. Step through the whole sequence — sixteen events, 372 milliseconds.

Step
1 / 16
Elapsed
0 ms

Client
0 Browser parses URL
32 TCP SYN
88 TLS ClientHello (incl. ALPN
168 TLS handshake complete; ALPN
172 HTTP/2 HEADERS frame
360 Browser receives 200 OK; def
Renderer
372 JS handler updates DOM with
DNS
8 DNS resolve toolkit.app
CDN/LB
188 CDN edge receives request, c
220 Edge
320 Edge passes through (no cach
Origin
232 Origin app receives request,
248 Query DB: SELECT * FROM orde
274 Render JSON, set Cache-Contr
280 HTTP 200 ← 6.4 kB JSON, gzip
Cache
4 Browser cache check
Now
Client
Browser parses URL → toolkit.app/api/orders
scheme=https, host=toolkit.app, path=/api/orders
All steps
t+0 Client Browser parses URL → toolkit.app/api/orders
t+4 Cache Browser cache check → MISS
t+8 DNS DNS resolve toolkit.app → 76.76.21.21
t+32 Client TCP SYN → 76.76.21.21:443
t+88 Client TLS ClientHello (incl. ALPN h2,http/1.1)
t+168 Client TLS handshake complete; ALPN selected h2
t+172 Client HTTP/2 HEADERS frame → :method=GET :path=/api/orders
t+188 CDN/LB CDN edge receives request, checks cache → MISS for /api/orders
t+220 CDN/LB Edge → origin LB on private network
t+232 Origin Origin app receives request, validates auth cookie
t+248 Origin Query DB: SELECT * FROM orders WHERE user_id=$1 LIMIT 50
t+274 Origin Render JSON, set Cache-Control: private, max-age=0
t+280 Origin HTTP 200 ← 6.4 kB JSON, gzip-encoded
t+320 CDN/LB Edge passes through (no cache)
t+360 Client Browser receives 200 OK; deflates JSON; calls onload
t+372 Renderer JS handler updates DOM with order list

What you're looking at

Six lanes, one for each actor a request touches: the client, the browser's renderer, DNS, the CDN edge, the origin server, and the local cache. Every dot is one event stamped with its cumulative time in milliseconds, the Now panel narrates the current step, and the log underneath keeps the full sixteen-event sequence in order. The Elapsed counter at the top tells you how deep into the 372 ms total you are.

Hit Next a few times, or Auto to let it run. Watch how long the Client lane stays busy before anyone else does real work: the TCP SYN doesn't leave until t+32, and the TLS handshake isn't finished until t+168, nearly half the budget spent before a single byte of HTTP goes out. The origin app first sees the request at t+232, runs its auth check and database query, and is done by t+280. That's 48 milliseconds of application work inside a 372 ms request. Everything else is the network clearing its throat.


How does an HTTP request actually work?

One slow image stalls the whole page.

The HTTP request lifecycle is the chain of work between a user clicking a link and the page rendering — DNS, TCP handshake, TLS handshake, HTTP request, server processing, response, parse, render. Tim Berners-Lee drafted the first HTTP spec in 1991; HTTP/1.1 (RFC 2616, 1997), HTTP/2 (RFC 7540, 2015), and HTTP/3 (RFC 9114, 2022) each rewrote the wire format to cut the latency.

Imagine a webpage with twenty things to fetch — the HTML, three stylesheets, a few scripts, a dozen images. Each fetch is an HTTP request that travels to a server somewhere on the planet and brings back a response. The browser asks for them; the server replies; the page renders. In 1996 those twenty fetches each opened a fresh TCP connection, did a DNS lookup, exchanged a TLS handshake, and tore everything down on the way back. Two hundred milliseconds per request added up to four seconds of overhead before any pixel reached the screen.

The first fix — “keep the connection open between requests” — landed in HTTP/1.1 in 1997. Persistent connections (the Connection: keep-alive header) let the same TCP socket carry many requests. But there was still a problem: each request waited for the previous response to come back before it could be sent. On a connection of latency 100 ms, twenty serial requests still cost two seconds of round-trips. Browsers worked around it by opening six parallel connections per origin, but that was a hack on top of a hack — and the third request on the third connection still couldn't start until the second response on that same connection had arrived.

Pipelining, the next attempt, let the browser send all twenty requests back-to-back without waiting. That sounds good, but the rules said the server had to return responses in the order requests arrived. So if request #1 was a slow database query and #2 through #20 were fast static files, every fast response was queued behind the slow one. This is head-of-line blocking, and it's the central pain HTTP versions 2 and 3 spent the next two decades fixing.

HTTP/2 (2015) wrapped many independent streams on one connection. The server could return responses in any order. A slow stream blocked only itself, not its neighbours. Header compression cut hundreds of bytes off every cookie-laden request. But TCP, the layer underneath, still served bytes in strict order — so a single dropped IP packet stalled every stream until retransmission completed. HTTP/3 (2022) finally pushed the fix down to the transport layer by replacing TCP with QUIC, a UDP-based protocol that lets each stream be reassembled independently. A lost packet stalls one stream, not all of them.

The simulator above shows where the milliseconds go in a real request: DNS, TCP, TLS, the wait for the server's first byte, the download, and the rendering. Most of the wins of HTTP/2 and HTTP/3 are about removing or shrinking those phases — keeping connections warm, multiplexing streams, and getting the first byte across faster on lossy mobile networks. The 1996 cold-start cost of four seconds is now a cold-start cost of about 200 ms — and a warm-start cost of nearly zero, which is why the modern web feels instant.


Origins of HTTP — Tim Berners-Lee 1991 to RFC 9114 (HTTP/3)

From Tim Berners-Lee's notebook to RFC 9114.

The Hypertext Transfer Protocol began as a one-page sketch. HTTP/0.9, written by Tim Berners-Lee at CERN in 1991, supported a single verb — GET — and returned a raw HTML document with no headers, no status codes, and no version string. The connection was closed at the end of the response. There was no Host field because every server hosted exactly one site. The whole protocol fit on a postcard.

HTTP/1.0 followed in RFC 1945 (May 1996), edited by Berners-Lee, Roy Fielding, and Henrik Frystyk Nielsen. It introduced status codes (200, 404, 500), request and response headers, the Content-Type field that let HTTP carry images and binary blobs as well as HTML, and the version line that gave the protocol future-proofing. Every connection still set up and tore down a TCP socket per request, which on a 1996 modem at 50 ms RTT was painful but tolerable. The web of hand-coded HTML tolerated it.

HTTP/1.1 arrived in RFC 2068 (1997) and was tightened into RFC 2616 (1999), then re-organised in 2014 into the six-document RFC 7230–7235 series (Message Syntax, Semantics, Conditional Requests, Range Requests, Caching, Authentication) and finally consolidated again in 2022 as the modern RFC 9110–9112 set. Persistent connections (Connection: keep-alive) became the default, the Host header enabled name-based virtual hosting (one IP, many sites — the engineering hack that made shared web hosting viable), chunked transfer encoding allowed responses of unknown length, and conditional GET via If-Modified-Since and ETag opened the door to validation caching. HTTP/1.1 ran the public web for two and a half decades.

The replacements stand on its shoulders. HTTP/2 (RFC 7540, 2015; obsoleted by RFC 9113, 2022) wraps the same semantics in a binary framing layer over a single TCP connection. HTTP/3 (RFC 9114, 2022) abandons TCP altogether for QUIC (RFC 9000), the UDP-based transport Google began deploying in production in 2013 and that the IETF formalised in 2021. The wire formats keep changing; the resource model — verbs, URLs, headers, status codes, content negotiation — has been stable since 1997.

The status-code namespace is a museum of design decisions. The 1xx range was set aside for informational responses (100 Continue, 101 Switching Protocols for the WebSocket upgrade). 2xx is success. 3xx is redirection, with 301 permanent and 302/307/308 handling the subtleties of method preservation. 4xx is the client's fault — 400 Bad Request, 401 Unauthorized, 403 Forbidden, 404 Not Found, 409 Conflict, 418 I'm a teapot (added as a joke in RFC 2324 and surprisingly persistent), 429 Too Many Requests. 5xx is the server's fault — 500 Internal Server Error, 502 Bad Gateway, 503 Service Unavailable, 504 Gateway Timeout. The codes have been remarkably stable; RFC 9110 §15 lists the canonical set, and any new code requires an IETF expert-review registration.


Head-of-line blocking and the HTTP versions that fight it

The fight against head-of-line blocking.

HTTP/1.1 allowed pipelining — sending multiple requests on one connection without waiting for the responses — but the server had to return responses in the order requests arrived. A slow first request blocked every subsequent one in the same connection, the textbook head-of-line blocking problem. Worse, intermediaries handled pipelining badly; many proxies rewrote or dropped pipelined requests entirely. By the early 2010s every major browser shipped pipelining disabled by default. Chrome opened a pool of six TCP connections per origin and round-robined requests across them; Firefox and Safari did the same. The "domain sharding" trick — splitting assets across img1.example.com, img2.example.com, etc. — was a workaround for the six-connection cap.

HTTP/2, derived from Google's SPDY experiment (2009–2015), replaced pipelining with multiplexing. A single TCP connection carries many independent streams, each identified by a stream ID; frames from different streams can interleave on the wire in any order, and the server can return responses in whatever order they finish. Header compression via HPACK (RFC 7541) eliminated the 600–1500 bytes of repeated cookie and User-Agent text that bloated every HTTP/1.1 request. Stream prioritisation hints — though the spec was loose enough that implementations diverged — let browsers tell the server which assets blocked rendering.

HTTP/2 still used TCP, and TCP imposes its own ordering. A single dropped IP packet stalls every stream on the connection until retransmission completes, because TCP reassembly is a single byte stream. This is transport-layer head-of-line blocking, the cousin of the application-layer kind HTTP/2 fixed. Mobile networks — with their 0.5–2% packet loss and 50–200 ms recovery times — pay this tax constantly.

HTTP/3 over QUIC fixes both at once. QUIC streams are independent at the transport layer too; a lost packet only stalls the streams it carried. QPACK (RFC 9204) replaces HPACK with a stream-safe variant: HPACK assumed in-order delivery of header frames so the decoder could reference earlier entries; QPACK splits its dynamic table updates onto a dedicated control stream so out-of-order streams don't block each other on header decompression. The protocol stack got harder to implement, but the user-visible behaviour got faster.

The fight against head-of-line blocking, begun with persistent connections in 1997, ends at the IP layer in 2022 — only because UDP gave QUIC the freedom to redesign the transport that TCP's ossification refused. TCP extensions like Multipath TCP (RFC 8684, 2020) and TFO (TCP Fast Open, RFC 7413, 2014) tried to deliver similar gains within TCP, but the long tail of middleboxes — corporate firewalls, ISP gateways, mobile carrier NATs — broke them. The lesson middleboxes taught the IETF was that the network can't be relied on to forward packets it doesn't recognise; QUIC encrypts most of its transport metadata precisely to make middlebox interference impossible by construction.

HTTP/1.1 PIPELINING vs HTTP/2 MULTIPLEXING · ON THE WIREHTTP/1.1 · HEAD-OF-LINE BLOCKINGrequest 1 (slow)req 2 …req 3responses must come back IN ORDERslow #1 stalls #2 and #3HTTP/2 · STREAMS INTERLEAVEs1s2s1s3s2s3frames carry stream-id; reorder freelyslow stream stalls only itselfHTTP/3 (QUIC) PUSHES THE SAME PRINCIPLE INTO THE TRANSPORT — A LOST PACKET STALLS ONE STREAM ONLY

Where the latency budget goes — DNS, TCP, TLS, server, render

Where the 372 ms actually goes.

A first-time API request from a North-American laptop to a North-American origin breaks down roughly: 8–25 ms in DNS, 30–50 ms in the TCP three-way handshake, 80–120 ms in the TLS 1.3 handshake (one round-trip plus crypto, per RFC 8446), 100–180 ms in the application path (auth, database, JSON), and the rest in network serialisation. The application is barely a third of total time on a cold connection.

On warm connections (HTTP/2 keep-alive), DNS is cached, TCP is open, TLS is negotiated. A subsequent request is one round-trip plus app time — often under 200 ms. The single biggest deployable win for end-user latency is connection reuse, not application optimisation. The TLS 1.3 zero-round-trip-time (0-RTT) extension, when both client and server support session resumption, allows the first request on a resumed connection to ship encrypted application data in the same packet as the ClientHello, saving an entire round-trip. The catch: 0-RTT data is replayable, so RFC 8446 forbids it for non-idempotent operations — you must be careful not to re-charge a credit card if a forwarder replays the early-data packet.

Every phase has a "When it dominates" answer that points at a different fix. DNS dominates on first-touch domains; the fix is preconnect hints (<link rel="preconnect">) and DNS-over-HTTPS resolvers like Cloudflare's 1.1.1.1 that median 10–14 ms. TCP dominates on cross-region requests; the fix is TLS session resumption plus QUIC's 0-RTT. TLS dominates on new origins; the fix is TLS 1.3 (cuts the handshake from two RTTs to one) and Encrypted ClientHello (ECH) for SNI confidentiality. Server processing time, the dreaded TTFB, dominates on real APIs; the fix is database indexes, async fan-out, and caching at the edge.

PhaseTypicalWhen it dominates
DNS lookup5–50 msFirst request to a new domain; cold resolver cache.
TCP connect1×RTTNo keep-alive; cross-region request.
TLS handshake1×RTT (TLS 1.3) or 2×RTT (TLS 1.2)First HTTPS to a new origin.
Request send≤ 1 ms (sub-MTU)Large POST body, slow uplink, mobile.
Server processing (TTFB)10–500 msDatabase query, fan-out — most "slow API" tickets live here.
Content downloadsize / bandwidthLarge response, congested link.
Render-blocking parse10–500 msBig JSON, heavy JS execution.
Idle / queuevariesBrowser hit per-origin connection limit (6 in HTTP/1.1).

Use curl -w '%{time_namelookup} %{time_connect} %{time_appconnect} %{time_starttransfer} %{time_total}\n' for command-line breakdown. Chrome's Performance tab and webpagetest.org give the visual version.


How a CDN cuts the round-trips

The CDN took the round-trips away.

The single biggest reason modern web requests feel fast is geography. A North American user reaching a European origin pays roughly 80–120 ms one-way at the speed of light through fibre, doubled to 160–240 ms per round-trip; with three round-trips in a fresh TLS connection that's nearly half a second before the first byte. The fix is a point of presence — a server park in the user's city that terminates TCP and TLS, holds a warm connection back to origin, and either serves a cached response or forwards. Cloudflare advertises 330+ cities, Akamai roughly 4,200 PoPs, Fastly around 80, AWS CloudFront about 600 edge locations. The user's round-trip becomes a few milliseconds; the long-haul piece happens once, on a connection the edge keeps open.

Caching at the edge applies the conditional GET machinery from RFC 7232ETag, If-None-Match, Last-Modified, If-Modified-Since — and the freshness rules from RFC 9111 (Cache-Control: max-age, stale-while-revalidate, stale-if-error). A fresh hit returns 200 from the edge with no origin contact. A stale-but-revalidatable hit returns the cached body and asynchronously refreshes; the user pays nothing. A miss is a "tiered fetch" up the cache hierarchy — edge to regional cache to origin shield to origin — which collapses thundering herds when a hot key expires.

Range requests (RFC 7233) let the edge stream a 1 GB video without buffering the whole file; the player asks for byte ranges and the edge serves them. CONNECT tunnels (used by HTTP proxies and the WebSocket handshake) carry arbitrary TLS through HTTP-aware infrastructure. The Alt-Svc header (RFC 7838) lets a server advertise that the same origin is reachable over HTTP/3; the browser caches the hint and uses QUIC on the next visit. This is how HTTP/3 rolled out without breaking anything — clients fell back to HTTP/2 if QUIC was blocked by a firewall, and upgraded silently when it wasn't.

The economics are stark. A typical Cloudflare or Fastly POP serves tens of gigabits per second from terabytes of NVMe with hit ratios of 95% or higher for static assets; an origin server fronted by such an edge sees only the 5% misses, plus a handful of revalidation conditional GETs. That ratio dictates that origin capacity can be sized at a fraction of total request volume — the user-visible web is mostly cache. Akamai's State of the Internet reports put 2024 cacheability at over 70% for HTML and over 95% for static media; the rest is API traffic that the edge proxies but doesn't store. Without that asymmetry the public web would not function at its current scale; with it, a small startup can serve a planetary audience by handing the bytes to someone else's edge.

PropertyHTTP/1.1HTTP/2HTTP/3
TransportTCP + TLSTCP + TLSQUIC (UDP) + TLS 1.3
Multiplexingno (pipelining buggy)stream-leveltransport-level
Server pushnoyes (deprecated 2022)removed in spec
Header compressionnone (gzip body only)HPACK (RFC 7541)QPACK (RFC 9204)
Connection setup2–3 RTT2–3 RTT1 RTT new, 0 RTT resumed
Connection migrationnonoyes (Wi-Fi → LTE)

The features that didn't survive contact.

HTTP/2's most marketed feature was server push: the server could pre-emptively send resources (CSS, JS) on the same connection before the client asked, anticipating what the HTML would request next. In practice push was almost impossible to use correctly. The server didn't know what was already in the browser cache, so it pushed redundant bytes; the prioritisation interaction with parser-blocking resources was subtle; the wire-time wins rarely showed up in field measurements. Chrome announced removal in late 2020 and disabled it by default in Chrome 106 (September 2022). Firefox followed. Server push is dead in production browsers; the spec lives on but no one ships it.

HTTP/2 pipelining never had the head-of-line problem of HTTP/1.1, but it inherited TCP's. A single packet loss stalls every stream on the connection. On clean networks this is invisible; on lossy networks (cellular, congested Wi-Fi) HTTP/2 can be measurably slower than HTTP/1.1 with parallel connections. Cloudflare's 2018 measurements showed HTTP/2 underperforming HTTP/1.1 on the worst 10% of mobile connections.

HTTP/1.1 pipelining itself was a casualty. The IETF kept the feature in RFC 7230 for backward compatibility, but every major browser disabled it by default years before. The only place pipelining ships in 2024 is curl with explicit flags and a few embedded HTTP libraries on devices that talk to a single trusted server.

Apple's App Transport Security (ATS), introduced in iOS 9 (2015), banned plaintext HTTP from iOS apps. NSAllowsArbitraryLoads let developers opt out, but App Store review increasingly required justification. By iOS 14 the only way to make a plain-HTTP request from a native app was to whitelist a specific domain in Info.plist with a security-review-friendly reason. Chrome on Android applies similar pressure: from Chrome 90 onward the address bar omits the https:// prefix and shows a "Not Secure" warning on plaintext sites. The transition from HTTP to HTTPS was an industry-coordinated push that happened in five years; the pre-2015 web was mostly plaintext, the post-2020 web is mostly TLS 1.3.

Even the headers that survived have new sharp edges. Cookie with SameSite=None requires Secure as of Chrome 80 (2020); the cross-site tracking debates of the 2010s reshaped what browsers will and won't carry over plaintext. The Strict-Transport-Security header (RFC 6797) lets a site instruct the browser to always use HTTPS for that origin for a given period — once set, even typing http:// cannot reach the origin in cleartext. Google's HSTS preload list (compiled into Chromium and Firefox) shipped this as the default for high-risk domains. Once-optional security postures have become protocol-level invariants.

HTTP/2 push: shipped, undone

Server push was the headline feature of HTTP/2 and is the canonical example of a protocol bet that didn't pay off. The server pushed bytes the browser already had cached; prioritisation against parser-blocking resources was subtle; field measurements rarely showed wins. Chrome 106 (Sep 2022) disabled it by default; the spec lives but no one ships it. The lesson: the wire is cheap, the cache is the constraint.


QUIC and HTTP/3 — a new transport, measured

A new transport, measured.

HTTP/3 over QUIC merges the transport handshake (was TCP) and the cryptographic handshake (was TLS) into one. A new connection is one round-trip; a resumed connection can be 0-RTT, with application data on the very first packet. On mobile networks where each round-trip costs 50–200 ms over the air, the savings are dramatic. Cloudflare published end-to-end measurements in 2020 showing HTTP/3 cutting median page-load time by 8–15% and 99th-percentile by 20% or more on lossy links. Google's deployment numbers (the original QUIC at Google began in 2013) put YouTube rebuffering events down by ~30% and Google Search latency at the long tail down by similar amounts.

Streams in QUIC are independent at the transport layer — lost packets only block the streams they belong to, not the whole connection. Connection migration, a feature TCP can't offer, lets a QUIC connection survive an IP-address change: a phone that switches from Wi-Fi to LTE keeps the same connection, the same TLS state, the same HTTP streams, no re-handshake. The connection ID is independent of the four-tuple; only the path validation needs a fresh round-trip.

The cost is non-trivial. QUIC is implemented in user-space, where the kernel's zero-copy fast path (sendfile, splice) doesn't apply, and the per-packet CPU cost is roughly 2–3× TCP. Linux 6.x has begun adding io_uring hooks and UDP segmentation offload (UDP_GSO) to recover the gap. Cloudflare's 2024 measurements show QUIC server-side CPU at roughly 1.4× HTTP/2 + TLS, narrowing every quarter. Meta, Google, and Cloudflare have all publicly stated they serve over half their HTTP traffic on HTTP/3 for clients that support it, with HTTP/2 the standard fallback over the same TCP+TLS path.

# Negotiation cascade
# 1. Browser opens TCP+TLS to https://example.com (HTTP/2 via ALPN h2)
# 2. Server sets:  Alt-Svc: h3=":443"; ma=86400
# 3. Browser caches hint, races QUIC + TCP on next request
# 4. If QUIC handshakes first, future requests use HTTP/3
# 5. If a UDP path goes dark, browser falls back to HTTP/2

# Inspect the negotiation:
$ curl -v --http3 https://cloudflare.com 2>&1 | grep -E "ALPN|HTTP/"
* ALPN: server accepted h3
> GET / HTTP/3
< HTTP/3 200
HPACK · STATIC TABLE (61 ENTRIES) + DYNAMIC TABLE → 1-BYTE HEADER REFERENCESSTATIC TABLE (read-only)2 :method GET3 :method POST7 :scheme https17 accept-encoding… 61 entries totalDYNAMIC TABLE (per-connection FIFO)62 user-agent: Chrome/126…63 cookie: sid=ab12c…64 :authority example.com→ second request: 1-byte refsvs ~600B HTTP/1.1 raw headers
# HTTP/2 SETTINGS frame (RFC 7540 §6.5)
+-----------------------------------------------+
| Length (24)         | Type (8)=4 | Flags (8)  |
+-----------------------------------------------+
| R |          Stream Identifier (31)=0         |
+-----------------------------------------------+
| Identifier (16) | Value (32)                  |
+-----------------------------------------------+
# parameters sent on connection establishment:
SETTINGS_HEADER_TABLE_SIZE        (0x1) = 4096
SETTINGS_ENABLE_PUSH              (0x2) = 0     # disabled
SETTINGS_MAX_CONCURRENT_STREAMS   (0x3) = 100
SETTINGS_INITIAL_WINDOW_SIZE      (0x4) = 65535
SETTINGS_MAX_FRAME_SIZE           (0x5) = 16384
SETTINGS_MAX_HEADER_LIST_SIZE     (0x6) = 8192

Core Web Vitals — the four numbers Google ranks you on

The four numbers Google ranks you on.

Performance work needs targets. Google's Core Web Vitals define four user-facing latency metrics, with good and poor thresholds chosen at the 75th percentile of real-world Chrome data. They are leading indicators of bounce rate, conversion, and search ranking; since 2021 Google has used them as a tie-breaking factor in the search algorithm. The targets aren't aspirational — they're measured continuously by the Chrome User Experience Report (CrUX), and Lighthouse audits in DevTools are calibrated against them.

TTFB is the only one that's purely server-side; the others depend on browser layout. Most "make the API faster" tickets are really TTFB tickets — and TTFB above ~1 s usually means the server is doing something synchronously that should be asynchronous (database call without an index, microservice fan-out without timeouts, no caching layer). Interaction to Next Paint (INP) replaced First Input Delay (FID) in March 2024 because FID measured only the first interaction; INP samples every interaction and reports the worst, which is what users actually feel.

The split between transport (controlled by HTTP version, edge presence, connection reuse) and rendering (controlled by JS bundle size, image format, layout reflow count) is roughly 40/60 for typical sites. A server-side fix can cut TTFB by hundreds of milliseconds; a client-side fix is usually how LCP and INP move. Both matter, and both are measurable: the PerformanceNavigationTiming and PerformanceObserver APIs expose every milestone the browser knows about, in the same units the Web Vitals thresholds use.

One under-appreciated trick: field data beats lab data. Lighthouse runs on a fast machine with a cold cache and a simulated mobile network; it's reproducible and useful for regression testing, but it doesn't look like your median user. The web-vitals JavaScript library (open-source from Google) reports real LCP, INP, CLS, TTFB from the user's browser back to your analytics endpoint, weighted by actual session distribution. Real User Monitoring tools — SpeedCurve, Datadog RUM, New Relic Browser, Sentry Performance, Cloudflare Web Analytics — aggregate those reports and produce 75th-percentile numbers comparable to CrUX. The 75th-percentile-not-the-median rule comes from the search ranking signal: Google considers a page "good" only when 75% of visits clear the threshold, which in practice means designing for the slow user and the slow connection.

MetricGoodPoor
LCP (Largest Contentful Paint)≤ 2.5 s> 4.0 s
INP (Interaction to Next Paint)≤ 200 ms> 500 ms
CLS (Cumulative Layout Shift)≤ 0.1> 0.25
TTFB (Time to First Byte)≤ 800 ms> 1.8 s

Further reading on HTTP request flow

Primary sources, in order.

Found this useful?