Essay · Performance
Designing for the first hundred milliseconds
A practical budget for the moments before a page becomes useful.
The first hundred milliseconds of a page load are mostly negotiation. Before a single pixel appears, the browser has to find the server, open a connection, agree on encryption, and ask for the document. On a fast office network this can happen in the time it takes to blink. On a phone in a train station, the same sequence can take a second or more. Designing for the first hundred milliseconds means designing for the round trips, not the rendering.
The budget
Think in round trips rather than milliseconds. A cold DNS lookup is one or more round trips. A TCP handshake is one. A TLS 1.3 handshake is one more. Only then does the request for the HTML leave the browser. At 20 milliseconds of latency, three round trips cost 60 milliseconds; at 150 milliseconds, the same three cost nearly half a second. The numbers are approximate, but the shape is not: latency multiplies everything.
- Reuse connections where you can; a warm connection skips DNS and the handshake.
- Keep certificate chains short and renewal automatic; a broken chain is a full stop.
- Preconnect to origins you know you will need, but do not open connections you will not use.
- Prefer a single origin for critical assets; parallel connections are not free.
Round trips are the currency
HTTP/2 multiplexes many requests over one connection, which removes the old queue of six parallel connections but does not remove the cost of establishing that connection. HTTP/3 moves the transport to QUIC and reduces head-of-line blocking, yet it still has a handshake. The wins are real, but they are not a substitute for fewer requests and smaller responses.
Redirects are round trips. A chain of three redirects is three chances to lose the user. Third-party scripts are round trips to machines you do not control. A font from another domain is a DNS lookup, a connection, and a request before the text can render in the face you intended. Every one of these can be justified; most of them are not.
What to cache
Caching is how you spend yesterday’s round trip today. Static assets with content hashes can be cached for a year, because their names change when their contents change. HTML is different: it should be revalidated, or cached briefly with a stale-while-revalidate window. The mistake is to cache everything forever and then invent a complicated purge system to undo it.
A cache is a promise that the world will not change while you are not looking.
When the promise is wrong, users see stale prices, old instructions, or a page that no longer matches the API. Set the promise deliberately, and make the invalidation path as simple as the caching path.
Make the first screen honest
The fastest page is the one that shows the thing the user came for. That usually means less: fewer blocking stylesheets, fewer web fonts, fewer scripts that must execute before the first paint. System fonts are not a compromise; they are often the fastest and most readable choice. Critical CSS can be inlined, but a single small stylesheet is easier to reason about and cache.
Do not hide latency behind a spinner. A spinner is an admission that the page has nothing to show yet. If the content is not ready, show the structure and the parts that are; if it is ready, show it. Perceived performance is not a trick. It is the difference between a page that respects the user’s time and one that performs respect.
Measure the right thing
Synthetic tests are useful for catching regressions, but they are not your users. Real-user monitoring, segmented by connection type and device, tells you what the first hundred milliseconds actually cost. Look at the median and the tail; the tail is where the frustration lives. Test on a mid-range phone, on a throttled network, with a cold cache. If it feels fast there, it will feel fast everywhere.
The goal is not a fast benchmark. It is the feeling that the page was already there.