cf.completefrontend
My progress
FUNDAMENTALS · MODULE 2

From URL to pixels

Everything that happens between pressing Enter and seeing a page: DNS, TCP, TLS, the HTTP exchange, redirects, parsing, and subresources, with where the time goes on a real phone, so you know which part to fix when a page is slow.

The problem: two seconds of white

Priya is on a train, holding a three-year-old Android phone. A friend sends her a link to a recipe, and she taps it. The browser opens, the address bar fills in, and then the screen stays white for two long seconds.

At about 2.8 seconds the title and the first paragraph appear. The big photo of the dish shows up at 4.2 seconds.

The recipe site's team is puzzled. On their laptops, on office Wi-Fi, the same page appears in 600 ms, and their server renders it in 200 ms. So where did more than two seconds of white go?

Most of it went to the network, before the browser had a single byte of HTML to work with. The phone had to find the server's address, open a connection, agree on encryption, follow a redirect to a second address and do some of that again, and only then ask for the page. Each of those steps is a message that travels to a server and back, and on a train every trip takes about 150 ms, often more.

The journey in one list

Here is everything that happens between tapping a link and seeing the page, in order. The rest of this lesson takes each step apart, with real numbers.

  1. Before the network. The browser decides whether the text is a URL or a search, upgrades http to https if it knows the site wants that, and checks whether a service worker or the HTTP cache can answer without the network.
  2. DNS. It turns the name recipes.example into an IP address such as 203.0.113.10.
  3. TCP. It opens a reliable connection to that address (or, with HTTP/3, a QUIC connection over UDP).
  4. TLS. It agrees on encryption keys with the server and checks the server's certificate.
  5. HTTP. It sends the request (GET /pasta HTTP/2 with headers) and waits for the response. The first byte of the answer marks TTFB, the time to first byte.
  6. Redirects. If the answer is 301 or 302, it goes back to step 2 or 3 for the new address.
  7. Parsing and subresources. It reads the HTML as it streams in, finds the CSS, JavaScript, fonts, and images it names, and fetches them, often from other servers that need their own DNS, TCP, and TLS.
  8. Rendering. It builds the DOM and styles, lays out the page, and paints it. The first text or image on screen is FCP, the first contentful paint. The largest one, often the hero image, is LCP, the largest contentful paint.
The journey from URL to pixels in three layers. First, in the browser, four shortcuts that can answer without the network: HSTS, the back/forward cache, a service worker, and the HTTP cache. Second, on the network, four steps that each wait on round trips: DNS turns the name into an address, TCP takes one round trip, TLS 1.3 one more, and the HTTP request waits for the first byte; a 3xx redirect sends the browser back to DNS. Third, on the device: parse the HTML and fetch the files it names, then render style, layout, and paint into the page on screen.1. In the browser: shortcuts firstHSTSbfcacheservice workerHTTP cache2. Network: each step waits on round tripsDNSname to IPTCP1 round tripTLS 1.31 round tripHTTPwait for TTFB3xx redirect: back to DNS3. Device: CPU workParsefind filesRenderlayout, paintrecipes.example
The middle layer is mostly waiting, so it grows with the round-trip time. The bottom layer is CPU work, so it grows on cheap phones. A redirect repeats the middle layer.

Why a frontend engineer should care

It is tempting to treat steps 2 to 6 as "the backend's problem". But many of the choices that make them slow are frontend choices: how many different domains the page loads from, whether the link points at the final URL or one that redirects, whether the HTML is streamed or built all at once, and which files the browser can discover early.

The rendering half (parsing, render-blocking files, style, layout, and paint) has its own lesson, the critical rendering path. This lesson spends its depth on the network journey and on the timeline of a whole page load: what each phase costs, how to measure it, and what to change when one is too slow.

Think first

The page appears in 600 ms on office Wi-Fi and in 2.8 seconds on the train. The server time and the bytes are the same. What is most likely different?

The mental model: a chain of round trips

Keep one picture in your head: a page load is a chain of round trips. A round trip is one message going from the phone to a server and one answer coming back. The time it takes is the RTT, the round-trip time. Most steps of the journey cannot start until the step before it has had its answer, so their RTTs add up.

Think of a cook who sends a runner to a market 15 minutes away. The runner can carry a lot, so the size of the shopping list barely matters. What matters is how many times the runner has to go back: once for the flour, then again because the recipe card in the flour bag says "you also need eggs", then again for the spice the eggs' label mentions. Three trips is 90 minutes, whatever the runner carries.

Pages work the same way: the HTML names the CSS, the CSS names a font, and the JavaScript fetches data that names an image. Each "you also need" is another trip.

Latency and bandwidth are different things

Bandwidth is how many bytes per second the connection can carry once data is flowing. Latency is how long one message takes to arrive; RTT is twice that. Phone plans advertise bandwidth, but for loading a page, latency usually matters more.

Take a page whose HTML is 30 KB after compression. On a 10 Mbps connection, sending 30 KB takes about 24 ms. Getting to the point where the server can send it (DNS, TCP, TLS, and the request) takes four round trips, which is 600 ms at 150 ms each. Doubling the bandwidth saves 12 ms; halving the RTT saves 300 ms.

Rough, real-world ranges. Lighthouse simulates the slow 4G row (150 ms RTT, 1.6 Mbps) when it scores a page for mobile.
ConnectionTypical RTTTypical download speedFour round trips cost
Office fibre, nearby server5 to 20 ms100+ Mbps20 to 80 ms
Home cable or DSL15 to 40 ms20 to 300 Mbps60 to 160 ms
Good 4G40 to 70 ms10 to 50 Mbps160 to 280 ms
Slow 4G or busy cell (Lighthouse mobile profile)150 ms1.6 Mbps600 ms
3G or weak signal300 to 600 ms0.4 to 1 Mbps1.2 to 2.4 s
A bar chart of typical round-trip times, to scale, with the cost of four sequential round trips. Office fibre: 15 ms, four trips 60 ms. Home cable: 30 ms, 120 ms. Good 4G: 55 ms, 220 ms. Slow 4G, the Lighthouse mobile profile: 150 ms, 600 ms. 3G or a weak signal: 400 ms, 1.6 seconds.one round trip, to scalefour round tripsoffice fibre15 ms60 mshome cable30 ms120 msgood 4G55 ms220 msslow 4G (Lighthouse mobile)150 ms600 ms3G or weak signal400 ms1.6 s
DNS, TCP, TLS, and the request are four round trips before the first byte of HTML. The same four cost 60 ms on office fibre and more than a second and a half on a weak signal.

Why the RTT is so high on a phone

Part of it is physics. Light in optical fibre travels about 200,000 km per second, so 1,000 km of cable adds about 5 ms each way, 10 ms per round trip. A user in Mumbai talking to a server in Virginia, about 13,000 km away, can never get an RTT below about 130 ms, and real routes are longer than straight lines. That is why CDNs put servers close to users.

The rest comes from the mobile network itself. The radio has to get a time slot from the cell tower, the signal goes through the carrier's core network before it reaches the internet, and a phone that has been idle needs to wake its radio first, which can add 100 ms or more to the first request. On a crowded or moving cell, packets get lost and are sent again, and each loss costs another round trip.

A formula you can do in your head

To estimate any phase of a page load, add four things. You will use this formula in every chapter.

  • Sequential round trips times RTT. Count only the trips that must happen one after the other. Trips that happen in parallel count once.
  • Bytes divided by bandwidth. Only for the big files; small files are lost in the noise.
  • Server time. How long the server thinks before it starts answering: a cache hit at a CDN is 1 to 5 ms, a page built from a database can be 100 to 500 ms.
  • CPU time on the device. Parsing HTML, running JavaScript, layout, paint, and image decoding. A budget Android phone does the same work 3 to 5 times slower than a recent laptop.

Check your understanding · Exercise 1

A page's first paint on slow 4G (150 ms RTT, 1.6 Mbps) is 2.6 s. Its HTML is 20 KB and its only CSS file is 15 KB, served from a separate static. domain. Which changes would cut the first paint by more than 100 ms? Select all that apply.

Shortcuts first, then DNS

What the browser checks before using the network

The fastest network request is the one the browser never makes, so it tries a few shortcuts first. They take a millisecond or two, and each one can skip most of the journey.

  • URL or search? The address bar decides whether pasta recipes is a search and recipes.example/pasta is an address. Chrome may even start connecting to a likely site while you are still typing.
  • HSTS and HTTPS upgrades. If the site sent a Strict-Transport-Security header (HSTS, HTTP Strict Transport Security) on an earlier visit, or is on the HSTS preload list built into browsers, the browser rewrites http:// to https:// itself, with no request. DevTools shows this as a 307 Internal Redirect. Recent versions of Chrome also try https first for any other http link and fall back to http only if that fails.
  • Back/forward cache. On Back or Forward, the browser may restore the whole previous page from memory, frozen as it was. No network, no parsing, near-instant.
  • Service worker. If the site installed one, it gets the request first and may answer from its own cache.
  • HTTP cache. If a fresh copy of the HTML is cached, the browser uses it. Many sites mark HTML no-cache, so the browser still has to ask the server whether it changed, but the files the HTML names often come straight from the cache.

If none of these answer, the browser needs to talk to a server. The URL holds a name, recipes.example, but the network routes packets to numbers. Turning one into the other is the job of DNS, the Domain Name System.

The resolver chain

DNS is a chain of caches with an authority at the end. The browser checks its own cache, then asks the operating system, which asks a recursive resolver, a server run by your internet provider, your company, or a public service such as 1.1.1.1 or 8.8.8.8. If the resolver has the answer cached, it replies at once. If not, it does the walk itself:

  1. It asks a root server "who knows about .example?" The root answers with the servers for that top-level domain (TLD). Resolvers keep this answer for days, so this step is almost always cached.
  2. It asks a TLD server "who knows about recipes.example?" The TLD answers with the site's authoritative name servers, often run by the DNS host or CDN.
  3. It asks the authoritative server "what is the address of recipes.example?" and gets the real answer, such as A 203.0.113.10, along with a TTL, the number of seconds the answer may be cached.
  4. It caches every answer for its TTL and passes the final address back to the operating system, which caches it and passes it to the browser, which caches it too.
A flowchart of a DNS lookup for recipes.example. The browser checks its own DNS cache, a hit costs 0 ms; on a miss, the operating system cache, also about 0 ms; on a miss, the recursive resolver, a hit costs one round trip. If the resolver has no copy, it walks: it asks a root server who has .example, the TLD server who has recipes.example, and the authoritative server, which answers A 203.0.113.10. The answer, with a TTL of 300 seconds, travels back and every layer caches it for its TTL. The cold walk adds about 100 to 300 ms.nearest cache firstBrowser cachehit: 0 msmissOS cachehit: about 0 msmissRecursive resolverhit: 1 round tripmiss: the cold walkRoot1. who has .example?TLD .example2. who has recipes?Authoritative3. A 203.0.113.10203.0.113.10, TTL 300 severy layer keeps it for 300 s
Most lookups stop at a cache. Only a name nobody nearby asked for within its TTL pays for the walk on the right, and even then the root and TLD answers are usually cached, so the resolver often asks only the authoritative server.

A popular name is almost always in the resolver's cache, because many other users asked for it recently. So a typical lookup costs one round trip to the resolver: 1 to 5 ms at home on fibre, 30 to 150 ms on a phone network. A name nobody nearby has asked for recently pays for the full walk, often 100 to 300 ms more, while a name in the browser's or the OS's own cache costs nothing.

The records a frontend engineer meets

RecordWhat it saysExampleWatch out for
AThe IPv4 address of a namerecipes.example A 203.0.113.10Several A records let the client pick another address if one fails.
AAAAThe IPv6 address of a namerecipes.example AAAA 2001:db8::10Browsers look up A and AAAA together and race connections to both ("Happy Eyeballs") so a broken IPv6 path does not stall the page.
CNAMEThis name is an alias for another namewww.recipes.example CNAME recipes.cdn-host.netOn a cache miss, the resolver must look up each alias in the chain in turn. Keep chains to one hop.
HTTPSHow to connect: supported protocols such as h3, alternative endpoints, and morerecipes.example HTTPS 1 . alpn=h2,h3Lets a browser that reads it use HTTP/3 on the very first visit instead of learning about it from a response header.

TTLs: the speed against agility trade

The TTL is set by whoever runs the domain, and it is a real choice. A long TTL, say 3600 (one hour) or 86400 (one day), means more lookups are answered from caches, which is faster and puts less load on the authoritative servers. But if you need to move the site to a new IP address, some users keep using the old address until their cached copy expires.

A short TTL, 30 to 300 seconds, lets a CDN or load balancer steer users quickly: away from a failing data centre, or to the nearest healthy edge. The cost is more lookups that miss the cache. CDNs commonly use short TTLs on the names they hand out for exactly this reason.

How DNS sends users to a nearby server

CDNs use DNS to put users near a server in two ways. With GeoDNS, the authoritative server looks at where the request came from and returns the address of a nearby edge, so a user in Mumbai and one in London get different answers for the same name. With anycast, the CDN announces the same IP address from hundreds of locations, and internet routing delivers each user's packets to the closest one. Many CDNs combine both.

Browsers can also send DNS queries encrypted (DNS over HTTPS, or DoH) to resolvers such as 1.1.1.1, so the network in between cannot read or change them. If that resolver is further away than the provider's own, a CDN using GeoDNS may pick a less nearby edge.

Starting DNS early from your page

You cannot speed up the lookup for your own page's name: it happens before your HTML arrives. But your HTML can tell the browser about other names it will need soon, so the lookup (and even the connection) happens while the browser is busy parsing.

html
<!-- Look up the name only: very cheap, fine for many origins -->
<link rel="dns-prefetch" href="https://images.recipes-cdn.example">

<!-- Look up, connect, and do TLS now: use for 1 to 3 origins you need in the first second -->
<link rel="preconnect" href="https://fonts.recipes-cdn.example" crossorigin>
Fonts are always fetched in CORS mode without credentials, and browsers keep such requests on a separate connection, so the preconnect for a font origin needs crossorigin to be the one the font uses.

Check your understanding · Exercise 2

Your site's A record has a TTL of 86400. At 10:00 you point it at a new server and shut the old one down at 10:05. Which statements are true? Select all that apply.

Opening a connection: TCP, TLS 1.3, and QUIC

The browser now has an IP address. Before it can send GET /pasta, it needs a connection that is reliable (nothing lost, nothing out of order) and private (nobody in between can read or change it). Over HTTP/1.1 and HTTP/2 that takes two handshakes, TCP and then TLS; HTTP/3 merges them into one.

TCP: one round trip to say hello

TCP (Transmission Control Protocol) numbers every byte, so the receiver can put them back in order and ask again for anything lost. To agree on starting numbers, the client sends SYN, the server answers SYN-ACK, and the client sends ACK with its first data right behind it. So the handshake costs one round trip.

TLS 1.3: one more round trip for privacy

TLS (Transport Layer Security) is the S in HTTPS. It proves the server really is recipes.example and sets up the keys that encrypt everything after it. TLS 1.3, which almost every browser and CDN uses today, does both in one round trip:

  1. ClientHello. The browser lists its ciphers, the server name it wants (SNI, so one IP address can host many sites), the protocols it speaks (ALPN, for example h2), and its half of a key exchange, the key share. Sending the key share this early is the main change in 1.3.
  2. ServerHello and the rest. The server picks the cipher and protocol and sends its own key share, so both sides can compute the same secret key. Then, encrypted, it sends its certificate, a signature proving it holds the certificate's private key, and Finished.
  3. Client Finished plus the request. The browser checks that the certificate is signed by a trusted authority, covers recipes.example, and has not expired, then sends its own Finished with the encrypted HTTP request in the same flight.
A sequence diagram of a new HTTPS connection between the browser and the server, in three round trips. TCP: the browser sends SYN and the server answers SYN-ACK. TLS 1.3: the browser sends ACK with its ClientHello, carrying a key share, the server name, and the protocols it speaks; the server answers with ServerHello and its key share, then, encrypted, its certificate and Finished. Request: the browser sends its encrypted Finished together with GET /pasta, and the server answers 200 OK with the first 14 KB of HTML.BrowserServerTCP1 round tripTLS 1.31 round triprequest1 round tripSYNSYN-ACKACK + ClientHello: key share, SNI, ALPNServerHello, key share, {cert, Finished}{Finished} + {GET /pasta}{200 OK, first 14 KB of HTML}
Braces mark encrypted messages. TLS 1.3 sends the key share in the very first hello, so encryption is ready after one round trip, and the request rides along with the client's Finished. TLS 1.2 needed one more round trip here.

TLS 1.2 needed two round trips, because the key exchange only started after the first hello. Saving that round trip on every new connection is the main reason HTTPS now costs so little extra.

Resumption and 0-RTT

After a full handshake, the server can hand the browser a session ticket that skips the certificate exchange next time, though resuming this way still takes one round trip. With 0-RTT (zero round-trip) resumption, the browser sends its request in the very first flight, encrypted with a key from the last session.

The catch is replay: an attacker who records that flight can send it again, and the server cannot easily tell. So servers and CDNs accept 0-RTT data only for requests that change nothing when repeated, like a GET for a page. In browsers it is most common with HTTP/3.

Slow start: why the first 14 KB are special

A new TCP connection does not know how fast the path is, so the server starts carefully. It sends a first burst of about 10 packets, roughly 14 KB, then waits for the client to confirm them. Each round trip without loss, it doubles the burst: 14 KB, then 28, then 56. This is called slow start.

So on a fresh connection, a 14 KB response arrives in one round trip, a 40 KB one needs two, and a 100 KB one three or four, whatever the bandwidth. Hence the advice to fit the critical part of the page (the head, inline critical CSS, the first screen of HTML) in the first 14 KB.

QUIC and HTTP/3, in passing

QUIC is a newer transport that runs on UDP with TLS 1.3 built in, and HTTP/3 is HTTP on QUIC. One handshake sets up transport and encryption together, so a new connection costs one round trip instead of two, and resumption can be 0-RTT. QUIC also fixes two weaknesses of TCP:

  • No head-of-line blocking across requests. Over TCP, one lost packet stalls every HTTP/2 request on the connection until it is resent. QUIC gives each request its own stream, so a loss stalls only that stream.
  • Connection migration. A TCP connection dies when the phone moves from Wi-Fi to mobile data and its IP address changes. QUIC identifies a connection by an ID, so it can survive the switch when both sides support migration.

A browser learns that a server speaks HTTP/3 from an Alt-Svc: h3=":443" header on an earlier response (so the first visit still uses TCP), or, if it reads them, from the HTTPS DNS record. Where a network blocks UDP, browsers fall back to HTTP/2 over TCP.

Each row adds one round trip for the request and response itself, plus server time. Add another RTT or more if DNS is not cached.
SetupRound trips before the request is sentTime to first byte at 150 ms RTT (DNS cached, 50 ms server)
TCP + TLS 1.23 (TCP 1, TLS 2)about 650 ms
TCP + TLS 1.32 (TCP 1, TLS 1)about 500 ms
QUIC (HTTP/3)1about 350 ms
QUIC with 0-RTT resumption0about 200 ms
Reused open connection0about 200 ms
Round trip
DNS
Time to first byte at 150 ms per round trip, DNS cached, with 50 ms of server time. TCP + TLS 1.2: 650 ms. TCP + TLS 1.3: 500 ms. QUIC, HTTP/3: 350 ms. QUIC 0-RTT: 200 ms. reused connection: 200 ms.TCP + TLS 1.2650 msTCP + TLS 1.3500 msQUIC, HTTP/3350 msQUIC 0-RTT200 msreused connection200 msDNSTCPTLS or QUICrequestserver
At 150 ms per round trip, TLS 1.3 saves 150 ms over TLS 1.2 on every new connection, and QUIC saves another 150 ms. The slower the network, the more each saved round trip is worth.

Reusing connections

Handshakes are paid per connection, not per request. HTTP/2 and HTTP/3 carry many requests at once on one connection, so later requests to the same origin ride on it for free. HTTP/1.1 carries one request at a time, so browsers open up to 6 connections per host, each with its own handshakes.

So the number of origins (scheme plus host plus port) a page uses matters: every new origin can mean a new DNS lookup, TCP handshake, TLS handshake, and slow start.

Decision Which protocols should the site serve?

HTTP/1.1 only

One request at a time, up to 6 connections per host

Pros

  • Works everywhere, simplest to debug

Cons

  • Up to six handshakes and slow starts per host
  • Invites domain sharding (spreading files over extra host names), which adds handshakes

HTTP/2 over TLS 1.3

One TCP connection per origin, many requests interleaved

Pros

  • One handshake per origin, supported by every current browser and CDN

Cons

  • A lost packet stalls every request on the connection
  • Breaks when the phone changes network

HTTP/3 with HTTP/2 fallback

QUIC over UDP, falling back to HTTP/2 when UDP is blocked

Pros

  • One-round-trip setup, 0-RTT on return visits
  • No cross-request head-of-line blocking

Cons

  • More server CPU per byte
  • Some corporate networks block UDP

What we pick: Serve HTTP/2 and HTTP/3 together, which any major CDN does with a setting. HTTP/3 helps most where users suffer most: high RTT and lossy mobile networks. Keep HTTP/1.1 only as the last fallback, and stop domain sharding if you still do it.

Check your understanding · Exercise 3

A first-time visitor on slow 4G (150 ms RTT) opens https://recipes.example/ over HTTP/2 and TLS 1.3. The name is not in the phone's cache but is in the provider's resolver. The server answers in 50 ms. Which statements are true? Select all that apply.

The HTTP request, the response, and redirects

With an open, encrypted connection, the browser finally asks for the page. The request and response are plain structured text (HTTP/2 and HTTP/3 pack them into binary frames and compress the headers, but the meaning is the same). Reading them well is a daily skill: the Network panel in DevTools shows exactly these.

The request

http
GET /pasta HTTP/2
Host: recipes.example
User-Agent: Mozilla/5.0 (Linux; Android 10; K) ... Chrome/141.0.0.0 Mobile
Accept: text/html,application/xhtml+xml,*/*;q=0.8
Accept-Language: en-IN,en;q=0.9
Accept-Encoding: gzip, deflate, br, zstd
Cookie: session=8f2c...; consent=yes
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: cross-site
A navigation request. In HTTP/2 the Host line travels as the :authority pseudo-header, and Chrome's reduced User-Agent always says Android 10; K instead of the real version and model.
  • Accept-Encoding lists the compression formats the browser can undo. The server picks one, usually Brotli (br) for HTML.
  • Cookie carries every cookie set for this site, on every request to it. Over HTTP/1.1, 4 KB of cookies on 80 requests is 320 KB of upload on a phone's weak uplink; HTTP/2 and HTTP/3 compress repeated headers, but large cookies still cost on every new connection.
  • Sec-Fetch-* headers tell the server what kind of request this is. navigate plus document means a top-level page load, which servers can use to block attacks that load the page in other ways.

The response

The response headers for GET /pasta, one per line, with notes. HTTP/2 200: a full page, not a 304. content-type text/html: render it as a page. content-encoding br: Brotli, 120 KB sent as 26 KB. cache-control no-cache: keep it, but check next time. strict-transport-security: use https only from now on. server-timing db;dur=34: where the server time went.HTTP/2 200a full page, not a 304content-type: text/htmlrender it as a pagecontent-encoding: brBrotli: 120 KB sent as 26 KBcache-control: no-cachekeep it, but check next timestrict-transport-securityhttps only from now onserver-timing: db;dur=34where the server time went
The status and content-type decide what the browser does at all; content-encoding decides how many bytes travel. strict-transport-security removes the http redirect from every later visit.

The status line and headers arrive first, then the body. Three headers decide what the browser does next.

Content-Type: text/html tells it to render the body as a page (with Content-Disposition: attachment, it would download it instead). Content-Encoding: br tells it to decompress. Cache-Control tells it whether it may reuse this copy next time. A 304 Not Modified instead of 200 means "your cached copy is still good", and no body is sent.

What the server does inside TTFB

From the browser's point of view, the server's part of TTFB is a black box: the time between sending the request and the first byte coming back, minus one RTT. Inside the box, the cost varies wildly:

  • CDN cache hit: the edge has a copy, 1 to 5 ms.
  • CDN miss: the edge asks the origin, which may be on another continent. That adds the edge-to-origin round trip (often 50 to 200 ms, plus a new connection if none is open) and the origin's work.
  • Server-side rendering: the origin queries a database and APIs, then renders HTML. 100 to 500 ms is common; a slow API call makes it worse.

Two techniques let the browser start working before the server has finished. Streaming sends the HTML in pieces: the server flushes the head (with its CSS and script links) at once, then the body as data arrives. 103 Early Hints goes further: before the real response, the server sends an informational 103 response with Link headers, so the browser can preconnect and preload while the page is still being built. Chrome, Edge, and Firefox act on both kinds of hint, Safari only on preconnect, and other browsers ignore it.

http
HTTP/2 103
Link: </css/main.3f9a.css>; rel=preload; as=style
Link: <https://img.recipes-cdn.example>; rel=preconnect

HTTP/2 200
Content-Type: text/html; charset=utf-8
...
Early Hints: the browser starts fetching the stylesheet and connecting to the image CDN during the server's think time.

Redirects: the most expensive response with no content

A redirect is a response with a 3xx status and a Location header that says "ask over there instead". It has no useful content, but it costs a full request, and if the new URL is on another origin, a full new DNS lookup, TCP handshake, and TLS handshake too.

StatusMeaningMethod on the next requestCached by the browser?
301 Moved PermanentlyThe resource lives at the new URL from now onMay change POST to GETYes, by default
302 FoundGo there this timeMay change POST to GETOnly if headers say so
307 Temporary RedirectGo there this time, same methodKeptOnly if headers say so
308 Permanent RedirectMoved for good, same methodKeptYes, by default

Chains of redirects are common and easy to miss, because each hop is "just one small rule" owned by a different team. A link shared on social media can go through the platform's link shortener, then to http://recipes.example/pasta, then to the https version, then to the www host, then to a language-specific path. Four hops, three of them to new origins.

Two timelines to scale at 150 ms per round trip. Before: a shared short link costs 4 round trips and redirects to http, which costs 3 and redirects to https, which costs 3 and redirects to the www host, which costs 4 before its first byte: 14 round trips, about 2.1 seconds. After: the link points at the final www URL, 4 round trips, about 0.6 seconds.Before: a shared link with 3 redirectsshort link4 RTThttp://3 RTThttps://3 RTTwww (final)4 RTT2.1 sAfter: link to the final URLwww (final)4 RTT0.6 s
Each hop to a new origin pays DNS, TCP, TLS, and a request again. The page content is identical; linking to the final URL saves 1.5 seconds on slow 4G.
  • Link to the final URL everywhere you control: in emails, ads, sitemaps, and your own internal links.
  • Remove the http hop with HSTS. Send Strict-Transport-Security: max-age=31536000; includeSubDomains so returning visitors upgrade to https without a request. To cover first visits too, add preload and submit the domain to the preload list at hstspreload.org.
  • Pick one canonical host and redirect straight to the final URL in one hop, not one rule after another.
  • Avoid redirects that depend on the user, like sending / to /en/ based on Accept-Language. Serve the right content at /, or use links with the language in them from the start.

Check your understanding · Exercise 4

On slow 4G (150 ms RTT), a user follows http://recipes.example/pasta, which redirects to https://recipes.example/pasta, which redirects to https://www.recipes.example/pasta. Nothing is cached, the site is not on the HSTS preload list, and the link opens in an in-app browser that does not upgrade http links itself. Which statements are true? Select all that apply.

From the first byte to every file the page needs

The first bytes of HTML have arrived. This chapter follows them only as far as the network is concerned: how one HTML file turns into dozens of further requests, and in what order. Rendering itself belongs to the critical rendering path lesson.

Committing the navigation

In Chrome, the browser process makes the network request, not the tab. When the response headers say text/html, it picks a renderer process, the process that will run the page (on desktop, one per site, to keep sites apart). It streams the response to that process and commits the navigation: the address bar changes and the new document starts. Every browser starts parsing before the whole body has arrived.

Parsing as the bytes stream in

The HTML parser turns bytes into DOM nodes as they arrive, a chunk at a time. A script tag without async, defer, or type="module" pauses the parser until the script has downloaded and run, and a stylesheet in the head holds back the first paint until it arrives.

So that a paused parser does not stall the downloads too, browsers run a preload scanner: a light second reader that skims ahead in the raw HTML, spots src and href attributes, and starts those downloads early. It only finds URLs written plainly in the HTML.

Discovery chains

Every file the HTML names is a subresource. Some are named directly in the HTML; the browser finds those in the first pass. Others are only named inside another file, so the browser cannot even ask for them until that file has arrived and been read. Each link in such a chain adds at least one round trip, and often a new connection.

A graph of how files are discovered. The HTML names three files directly: the CSS, app.js, and the hero image, which are one step away. The CSS names a font, two steps away. app.js, once it runs, fetches JSON from an API, two steps away, and the JSON names a product image, three steps away. Each step costs at least one more round trip, plus a new connection if the file is on another origin.the pagestep 1step 2step 3HTMLCSSapp.jsherofontJSONimageeach step: 1+ RTTplus a new origin?plus its transfer
The hero image named in the HTML is found in the first pass. The image named in JSON is three steps deep, so it can never start before three more round trips. Preloading or server rendering moves deep files up to step 1.
  • Web fonts are named in CSS, and browsers only download a font once they know some text on the page uses it. So a font is at least two steps away from the HTML: HTML, then CSS, then font.
  • CSS background images are also found only after the CSS is parsed and an element using them is styled.
  • Images in client-rendered apps are the worst case: HTML, then the JavaScript bundle, then the JavaScript runs and calls an API, then the JSON names the image, then the image downloads. Four steps, each paying at least one RTT plus its own transfer time.

Priorities: who goes first

When many files are wanted at once on one HTTP/2 or HTTP/3 connection, the browser tells the server how important each one is. Chrome, for example, gives the highest priority to the HTML, the CSS in the head, and fonts; high priority to blocking scripts; and starts images at low priority, raising the ones that turn out to be in the viewport after layout. Async scripts and images below the fold stay low.

You can steer this. fetchpriority="high" on the hero image tells the browser it matters before layout proves it. rel="preload" makes a late-discovered file (a font, a background image, the LCP image of a client-rendered page) visible to the browser right away, cutting the chain short.

html
<head>
  <link rel="preconnect" href="https://img.recipes-cdn.example">
  <link rel="preload" href="/fonts/inter-latin.woff2" as="font" type="font/woff2" crossorigin>
  <link rel="stylesheet" href="/css/main.3f9a.css">
  <script src="/js/app.81c2.js" defer></script>
</head>
<body>
  <img src="https://img.recipes-cdn.example/pasta-1200.avif" fetchpriority="high"
       width="1200" height="800" alt="Bowl of tomato pasta with basil">
</body>
The hero image's origin is preconnected, the font is preloaded, the script is deferred, and the image is in plain HTML with a high priority.

Think first

A client-rendered product page (an empty div in the HTML, all content from a 300 KB script) has an LCP of 4.5 s on slow 4G. The image itself is only 80 KB. Where is the time going?

The timeline of a page load

You cannot fix what you cannot see. Browsers record a timestamp for every phase of the journey and expose them through the Navigation Timing API, plus a few paint timestamps through other performance APIs. Learning these names turns "the page is slow" into "DNS is fine, but the redirect costs 700 ms".

All times are milliseconds since the navigation started (startTime, which is 0). Each phase is the gap between two timestamps on one PerformanceNavigationTiming entry.

PhaseFromToWhat a big number means
RedirectsredirectStartredirectEndOne or more 3xx hops. Reported as 0 if any hop crossed origins, so check DevTools too.
Service workerworkerStartfetchStartThe service worker had to start up before it could handle the request.
DNSdomainLookupStartdomainLookupEndThe name was not cached nearby. 0 means it was.
TCP + TLSconnectStartconnectEndNew connection and handshakes. 0 means a connection was reused.
TLS alonesecureConnectionStartconnectEndThe TLS part of the line above.
Waiting for the serverrequestStartresponseStartServer think time plus one RTT.
Downloading the HTMLresponseStartresponseEndA large document, slow start, or a server that streams slowly.
Parsing until the DOM is readyresponseEnddomInteractiveBlocking scripts or a huge document.
Deferred scripts and DOMContentLoaded handlersdomInteractivedomContentLoadedEventEndHeavy defer scripts or DOMContentLoaded listeners.
Everything else, then loaddomContentLoadedEventEndloadEventEndImages, iframes, and late subresources still loading.
A waterfall of one page load on slow 4G with no redirect. DNS from 0 to 150 ms, TCP from 150 to 300, TLS from 300 to 450, waiting for the first byte from 450 to 800, downloading the HTML from 800 to 1000, parsing until domInteractive at 1300, deferred scripts and DOMContentLoaded until 1450, and subresources until the load event at 3.1 seconds. Markers: TTFB at 0.8 seconds, FCP at 1.6 seconds, LCP at 2.3 seconds.TTFBFCPLCPDNSTCPTLSwait for first bytedownload HTMLparse, domInteractiveDOMContentLoadedsubresources, load01 s2 s3 s
Each bar is the gap between two timestamps of one PerformanceNavigationTiming entry, such as domainLookupStart to domainLookupEnd. Over half of the time to first byte here is DNS and handshakes; the load event comes long after the page looks done.

Reading the numbers from the page

ts
const [nav] = performance.getEntriesByType("navigation") as PerformanceNavigationTiming[];

const phases = {
  redirect: nav.redirectEnd - nav.redirectStart,
  dns: nav.domainLookupEnd - nav.domainLookupStart,
  connect: nav.connectEnd - nav.connectStart,
  tls: nav.secureConnectionStart > 0 ? nav.connectEnd - nav.secureConnectionStart : 0,
  wait: nav.responseStart - nav.requestStart,
  ttfb: nav.responseStart, // since startTime, so it includes everything before it
  download: nav.responseEnd - nav.responseStart,
  domInteractive: nav.domInteractive,
  protocol: nav.nextHopProtocol, // "h2", "h3", or "http/1.1"
};

navigator.sendBeacon("/rum", JSON.stringify(phases));
Send the phases from real users to your own endpoint. nextHopProtocol tells you how many of them actually got HTTP/3.

Three milestones: TTFB, FCP, LCP

Out of all those timestamps, three milestones matter most, because they line up with what the user feels.

  • TTFB, time to first byte: responseStart. It includes redirects, service worker startup, DNS, connection, and the server's think time, so it is the total cost of the network journey to the HTML. Good is 800 ms or less.
  • FCP, first contentful paint: the first time any text, image, or non-white canvas appears. The end of the blank white screen. Good is 1.8 s or less.
  • LCP, largest contentful paint: when the largest image or text block in the viewport was painted, usually the hero image or the headline. The moment the page looks loaded. Good is 2.5 s or less. LCP is one of the three Core Web Vitals, and it is measured until the user first scrolls, taps, or types.
Thresholds from web.dev, judged at the 75th percentile of real page loads: 3 out of 4 visits must meet them.
MetricGoodNeeds improvementPoor
TTFB0.8 s or less0.8 to 1.8 sover 1.8 s
FCP1.8 s or less1.8 to 3.0 sover 3.0 s
LCP2.5 s or less2.5 to 4.0 sover 4.0 s

Read them as a chain: FCP can never come before TTFB, and LCP can never come before FCP.

If TTFB is 1.9 s, the page cannot have a good LCP on a phone no matter how lean the frontend is. If TTFB is 400 ms but FCP is 3 s, the time is going into render-blocking files, and the critical rendering path lesson is where to look. If FCP is fast but LCP is 2 s later, the largest element is being discovered or downloaded late.

The recipe page, step by stepStep 1 of 8: 0 ms: tap

A step-through of the recipe page loading on slow 4G. On the left, the phone screen; on the right, seven phases on a time axis from 0 to 4.2 seconds: the redirect hop, the HTTPS connection, the server wait, the CSS from a second origin, the font from a third origin, the first paint, and the photo download.recipes.examplewhite screenredirect hopHTTPS connectionserver waitCSS, 2nd originfont, 3rd originstyle, layout, paintphoto download01 s2 s3 s4 s
Priya taps the link. The browser finds nothing cached for recipes.example, so it needs the network. The screen stays white.

0 ms: tap: Priya taps the link. The browser finds nothing cached for recipes.example, so it needs the network. The screen stays white.

Lab numbers and field numbers

Lab tools (Lighthouse, WebPageTest, the DevTools Performance panel) load the page once under fixed conditions: good for debugging, because every run is comparable. Field data comes from real users, through your own code like the snippet above, the web-vitals library, or Chrome's public CrUX dataset. Only field data tells you what your users get, because only it includes their phones, their networks, and their caches.

Check your understanding · Exercise 5

Field data at the 75th percentile shows: redirect 0 ms, DNS 0 ms, connect 0 ms, requestStart to responseStart 1,400 ms, FCP 2.1 s, LCP 2.3 s. Which conclusions follow? Select all that apply.

Where the time goes on a slow phone

Let's go back to Priya on the train and account for every millisecond of her 4.2-second wait for the recipe photo. The conditions are the ones Lighthouse uses for its mobile score: 150 ms RTT, 1.6 Mbps download (about 200 KB per second), and a phone whose CPU is about 4 times slower than a developer laptop.

The page

  • The shared link is http://recipes.example/pasta, which redirects to https. It opens in the messaging app's built-in browser, which does not upgrade http links itself.
  • The HTML is 40 KB compressed, rendered on the server for each request in about 200 ms.
  • The stylesheet (30 KB) is on static.recipes.example, a second origin.
  • The heading font (40 KB) comes from a third-party font service, a third origin, and text waits for it.
  • The hero photo (150 KB) is on an image CDN, a fourth origin, as a plain img in the HTML.
  • A 350 KB JavaScript bundle loads with defer.

The accounting

A simplified model at 150 ms per round trip and 200 KB per second. The extra milliseconds in the CSS and font rows are transfer time plus a little parsing. It ignores packet loss and the time the JavaScript spends running, both of which make the real numbers worse.
StepCostClock
DNS for recipes.example1 RTT150 ms
TCP to port 80, request, 301 back (plain http, no TLS)2 RTT + 20 ms470 ms
New TCP and TLS 1.3 to port 4432 RTT770 ms
Request, 200 ms of server rendering, first byte1 RTT + 200 ms1,120 ms (TTFB)
CSS: DNS, TCP, TLS, request, 30 KB on the second origin4 RTT + 160 ms1,880 ms
Font: found in the CSS, then DNS, TCP, TLS, request, 40 KB on the third origin4 RTT + 250 ms2,730 ms
Style, layout, and paint on a slow CPUabout 100 ms2,830 ms (FCP)
Photo: connected early, but its 150 KB share the 1.6 Mbps link with 460 KB of other filesbandwidthabout 4,170 ms
Decode and paint the photoabout 50 msabout 4,220 ms (LCP)
The recipe page's 4.22 second critical path on slow 4G, split by cause. A stacked bar runs from 0 to 4.22 seconds, with TTFB at 1.12, FCP at 2.83, and LCP at 4.22 seconds. Below it, the totals to scale: round trips 2.1 seconds, bandwidth 1.69 seconds, server think time 0.22 seconds, and CPU on the phone 0.21 seconds.critical path, in orderTTFB 1.12 sFCP 2.83 sLCP 4.22 sround trips (handshakes, requests)2.10 sbandwidth (bytes on the wire)1.69 sserver think time0.22 sCPU on the phone0.21 s
Half of the wait is round trips and most of the rest is bandwidth. The server and the phone's CPU, where teams often look first, account for about a tenth of this load (before the JavaScript runs).

Three lessons fall out of the table. First, more than a quarter of the wait happens before the first byte of HTML, and almost none of it is the server's fault: it is the redirect and the handshakes. Second, the first paint is set by a chain of three origins (page, stylesheet, font), each paying four round trips. Third, the LCP is set by bandwidth: 610 KB at 200 KB per second is three seconds of transfer, and the photo was competing with a bundle more than twice its size.

The CPU side

The model leaves out the JavaScript's own cost, but a phone feels it. 350 KB of compressed JavaScript is around 1.2 MB of code once decompressed, and a budget Android phone can spend one to two seconds parsing, compiling, and running it on the same main thread that handles taps.

The page looks ready at 4.2 s but ignores the first tap until well after. For a client-rendered page, that CPU time also sits in front of the LCP, because the content only exists after the code runs.

Fixing it, one phase at a time

FixPhase it removesSaving on slow 4G
Link to the final https URL and add HSTSThe redirect hopabout 320 ms off everything
Cache the HTML at the CDN edge (or stream it)Server rendering time inside TTFBabout 180 ms
Serve HTTP/3One handshake round trip per new connection150 ms per origin
Serve the CSS from the page's own originDNS, TCP, and TLS for the stylesheetabout 450 ms
Self-host the font and preload it from the HTMLA whole origin, and the wait for the CSSnearly 0.9 s off FCP
fetchpriority="high" on the photoWaiting behind the JavaScript for bandwidthabout 1.3 s off LCP

With all six, the same page on the same phone gets a TTFB of about 470 ms, an FCP of about 930 ms, and an LCP of about 1.8 s. Not one byte of the page was removed. Try the fixes one at a time below, and switch networks to see why none of this shows up on office Wi-Fi.

Network
Fixes
The recipe page on Slow 4G, budget phone with no fixes. TTFB 1.12 s, FCP 2.83 s, LCP 4.22 s. Good is TTFB up to 0.8 s, FCP up to 1.8 s, LCP up to 2.5 s.TTFB1.12 sFCP2.83 sLCP4.22 s01 s2 s3 s4 s5 s
0 of 6 fixes on. The photo is waiting for bandwidth behind the JavaScript. Dashed lines mark the "good" threshold for each metric.

What breaks in production

  • Third-party tags creep in. Each tag manager, A/B test, chat widget, and analytics script brings its own origin. Sort the DevTools Network panel by its Domain column and count: more than about 10 origins in the first few seconds is a warning sign.
  • The long tail misses the cache. The home page is always cached at the CDN; the 50,000 recipe pages are not. Field TTFB for rarely visited pages can be several times the home page's.
  • DNS is a single point of failure. When the DNS provider Dyn was hit by a large attack in October 2016, many well-known sites were unreachable for hours though their servers were fine. Large sites now use more than one DNS provider.
  • Shared caches no longer help. Browsers now partition the HTTP cache by the top-level site, so a font from a popular public CDN is downloaded again for every site that uses it. The old argument that "users already have it cached" is gone, which is one reason teams self-host fonts and libraries.

Check your understanding · Exercise 6

A news site's field data at the 75th percentile: TTFB 2.1 s, FCP 2.6 s, LCP 3.0 s. Most visits come from social media links. Which are good first places to look? Select all that apply.

"What happens when you type a URL?" in an interview

This question is popular because it has no bottom. A junior candidate can give a correct answer in a minute; a senior candidate can talk for an hour and still be learning. The interviewer is checking two things: do you know the whole journey in the right order, and can you go deep on any part of it with real reasons and numbers.

The one-minute answer

Start with the whole path, briefly, so the interviewer sees you know the shape. Then offer to go deep. A good opening sounds like this:

  1. "The browser checks whether the input is a URL or a search, upgrades to HTTPS if HSTS says so, and tries the back/forward cache, the service worker, and the HTTP cache."
  2. "If it needs the network, it resolves the name with DNS: its own cache, the OS, then a recursive resolver."
  3. "It opens a TCP connection and does a TLS 1.3 handshake, one round trip each, or a single QUIC handshake for HTTP/3."
  4. "It sends the GET request; the server or CDN answers. A redirect sends it back to the DNS or connection step."
  5. "It streams the HTML into the parser, and the preload scanner fetches the CSS, scripts, fonts, and images it names. Render-blocking CSS and scripts gate the first paint."
  6. "It builds the DOM and CSSOM (the style tree), then runs style, layout, paint, and compositing. FCP is the first paint, LCP the largest element."
  7. "Where would you like me to go deeper: the network, the rendering, or performance?"

Then draw. A horizontal timeline with the phases as blocks (DNS, TCP, TLS, request, server, download, parse, paint) and the RTT written above the first few is the most useful drawing, because every follow-up question becomes "which block gets bigger or smaller?"

How deep to go, by level

LevelWhat a strong answer adds
JuniorAll the steps in the right order, with what each one is for. Knows DNS turns names into addresses, TLS encrypts, and CSS and blocking scripts delay the first paint.
Mid-levelCosts: round trips per step, TLS 1.3 against 1.2, slow start and the 14 KB idea, the effect of extra origins and redirects. Can read a waterfall in DevTools and name TTFB, FCP, and LCP with their thresholds.
SeniorThe trade-offs and the measurement: DNS TTLs and failover, HTTP/3 and 0-RTT with replay risk, CDN cache misses inside TTFB, Early Hints and streaming, discovery chains and priorities, field against lab data, and a plan for what to fix first on a slow phone.

Common mistakes

  • Going down the network stack instead of along the journey. ARP, Ethernet frames, and routing tables are rarely what a frontend interviewer wants. Mention that packets are routed and move on unless asked.
  • Forgetting the shortcuts. Caches at every layer (bfcache, service worker, HTTP cache, DNS caches, open connections) are what make the journey fast in practice.
  • Listing without costs. "Then TLS happens" is weaker than "then TLS 1.3, one more round trip, about 150 ms on a slow phone".
  • Stopping at the first paint. Subresources, LCP, and the JavaScript's CPU time are where most real pages lose their time.

Bringing it into a system design round

In a frontend system design interview, nobody asks you to recite the journey, but it shapes many decisions.

When the requirements mention global users, mobile users, or a fast first load, say how you would budget the time before the first byte: a CDN close to users, HTML cached or streamed at the edge, HTTP/2 and HTTP/3, few origins on the critical path, and no redirects on shared links. Then say how you would check it: Navigation Timing and LCP from real users, at the 75th percentile, split by country and connection type.

Check your understanding · Exercise 7

The interviewer asks: "Our checkout page has a 2.4 s TTFB for users in Southeast Asia. Our servers are in Frankfurt. What would you do?" Which points belong in a strong answer? Select all that apply.

Summary

  • A page load is a chain of sequential round trips. On a slow phone each one costs about 150 ms, so counting them predicts load time better than counting kilobytes.
  • A cold HTTPS visit pays DNS, TCP, TLS, and the request, about four round trips before the first byte. TLS 1.3 needs one round trip, TLS 1.2 two, and QUIC folds transport and TLS into one.
  • DNS is a chain of caches ending at an authority. TTLs trade speed and low load against how quickly changes and failovers reach users.
  • Every new origin and every redirect can repeat the whole connection cost. Keep the critical path on one origin and link to final URLs.
  • TCP slow start means the first round trip carries only about 14 KB, which is why the critical part of the HTML should come first and be small.
  • Discovery chains (HTML to CSS to font, or HTML to JavaScript to API to image) add round trips; preload, preconnect, server rendering, and fetchpriority shorten them.
  • Navigation Timing splits TTFB into redirect, DNS, connect, and server wait. Read TTFB, FCP, and LCP as a chain, and judge them on field data at the 75th percentile.

In the interview

Questions interviewers ask about this. Answer each out loud first, then compare.

Question 1

Walk me through what happens when you type https://shop.example into the address bar and press Enter.

Question 2

Why is a first visit on a phone so much slower than on a laptop, even when the page is small?

Question 3

What does TTFB include, and how would you bring it down?

Question 4

What are the trade-offs of short and long DNS TTLs?

Question 5

What does HTTP/3 change for page loads, and is 0-RTT safe?

References

Read next