What HTTP/1.1 could not do
One connection could carry one request at a time. A browser opened about six per origin and queued everything else behind them — so a page with sixty assets spent most of its life waiting in line. That single limitation produced a decade of workarounds: sprite sheets, concatenated bundles, inlined images, and domain sharding — splitting assets across cdn1, cdn2, cdn3 to get more parallel connections.
HTTP/2: one connection, many streams
Because a request is nearly free, most of the old advice inverts: do not shard domains (each one is another handshake), do not concatenate everything into one file (one changed byte invalidates the whole bundle), and do not inline images as data URIs (they cannot be cached separately and they bloat the document).
Smaller, well-cached files are now better than fewer large ones — up to a point: there is still per-request overhead, and a thousand modules is slower than fifty bundles.
| HTTP/1.1 | HTTP/2 | |
|---|---|---|
| Requests in flight | 1 per connection, ~6 per origin | all of them, on one connection |
| Headers | plain text, repeated in full | binary, compressed (HPACK) |
| Encoding | text | binary frames |
| Cost of a small file | a whole connection slot | a stream — nearly free |
The blocking that stayed
HTTP/2 removed head-of-line blocking at the HTTP layer, but it all runs on one TCP connection — and TCP delivers bytes in order. A single lost packet stalls every stream on that connection until it is retransmitted. On a good network you never notice; on a lossy mobile one, HTTP/2 can be slower than HTTP/1.1 with six connections.
HTTP/3 runs over QUIC, which is built on UDP and tracks each stream separately: a lost packet stalls only its own stream. It also merges the transport and TLS handshakes into one round trip, and survives a change of network — your connection continues when the phone moves from wi-fi to mobile data.
Server push is gone, preload is not
HTTP/2 Server Push let a server send a file before it was asked for. It was removed from Chrome in 2022: servers pushed things the browser already had cached, and the waste outweighed the gain. <link rel="preload"> and 103 Early Hints do the useful part — telling the browser what to fetch, and letting it decide.
HTTP/1.1 103 Early Hints
Link: </assets/app.8f3d21.css>; rel=preload; as=style
<link rel="preload" href="/fonts/space.woff2" as="font" crossorigin>
Sharding had a limit even then
Splitting sixty files across two origins on HTTP/1.1 halves the queue and costs one extra handshake — a clear win. A third origin halves it again by less and costs another handshake, and somewhere around there the arithmetic turns. The lab below is that arithmetic; it is worth doing once, because it is the same trade-off as adding a font host or an analytics domain today.
What to actually do
- Serve everything from one origin where you can; use
preconnectfor the one or two you cannot. - Bundle by change frequency, not into one file: vendor code and application code have different lifetimes.
- Keep compression on (
brwhere available), and keep the hashed-asset caching from the caching lesson. - Enable HTTP/3 if your CDN offers it — it costs nothing and helps exactly the users on the worst networks.