The DevTools Timing panel shows a vendor script spending 220 ms in Initial connection and SSL before a single byte of the script transfers, and the same pattern repeats for every third-party origin on the page.

Triage: Measure the Handshake Share

Connection overhead is paid per origin, not per request, so the number worth measuring is how much of your third-party time is spent before any payload moves.

  1. Open Network, disable the cache, throttle to Slow 4G, and reload.
  2. For each third-party origin, open the first request’s Timing tab and record DNS Lookup, Initial connection and SSL.
  3. Sum those three across all distinct third-party origins. That total is your handshake budget.
  4. Compare it against the summed Content Download for the same requests.
Handshake cost scales with origins, not requests A comparison of two pages with identical total third-party bytes. The first loads them from one origin and pays a single handshake. The second loads them from five origins and pays five handshakes, adding roughly 880 milliseconds of connection setup for exactly the same payload. A note records that this is why origin consolidation outperforms request reduction. 1 origin 220 ms setup 2 origins 440 ms setup 3 origins 660 ms setup 5 origins 1100 ms setup Identical bytes in every row. Only the number of distinct origins differs. Which is why counting origins is a better first audit than counting requests.

Root Cause: Connection Pools Are Scoped Per Origin

The browser maintains a connection pool per origin, and a cold origin requires a DNS lookup, a TCP handshake and a TLS negotiation before it can carry anything. On a mobile connection those three cost roughly one, one and two round trips respectively — so an origin you have never contacted costs about four round trips of pure latency before the first byte of the resource.

None of that is amortised across origins. A page pulling scripts from five vendor domains pays the sequence five times, in parallel where the browser can but competing for the same bandwidth. This is the mechanism behind the observation in the waterfall guide that origin count is a more useful audit target than request count: consolidating ten requests onto one existing origin saves nothing on handshakes, while moving one request off a sixth origin saves a full sequence.

preconnect pays the sequence early, during time the browser would otherwise spend parsing, so that when the request is finally issued the socket is already warm. dns-prefetch pays only the first of the three steps, which is much cheaper and much less valuable — it removes the lookup and leaves the handshake.

What each hint pre-pays The connection sequence is split into DNS lookup, TCP handshake and TLS negotiation. The dns-prefetch hint completes only the first step, which is inexpensive and removes roughly one round trip. The preconnect hint completes all three, removing roughly four round trips, but consumes a socket from a limited pool. A note records that a preconnect to an origin you do not use is a socket taken from one you do. dns-prefetch DNS only — cheap, removes about one round trip preconnect DNS + TCP + TLS — removes about four, but takes a socket no hint the full sequence is paid when the request is issued The socket is the scarce resource. Spending one speculatively is a real cost, not a free hint.

Resolution: Warm the Few, Resolve the Rest

Rank your third-party origins by how confident you are that they will be used and how early. Warm the top two or three with preconnect; give the remainder dns-prefetch, which costs a DNS query and no socket.

<!-- The origins you are certain about, and need soon. Two or three, not ten. -->
<link rel="preconnect" href="https://cdn.vendor.example" crossorigin>
<link rel="preconnect" href="https://analytics.vendor.example" crossorigin>

<!-- Lower confidence or later need: resolve the name, do not take a socket. -->
<link rel="dns-prefetch" href="https://widgets.other.example">
<link rel="dns-prefetch" href="https://fonts.other.example">

The crossorigin attribute on a preconnect is not optional for script and font origins. Without it the browser warms a connection in the wrong CORS mode, the real request cannot use it, and you have spent a socket and a handshake for nothing — the same class of near-miss as a mismatched preload, and equally silent.

For consent-gated vendors this is the one hint that remains appropriate. It transfers no payload and reveals only that you may contact an origin your privacy notice already names, while removing the handshake from the post-consent path where it would otherwise be most visible.

Verification: Zero in the Timing Panel

Reload cold and open the Timing tab for a warmed request. Initial connection and SSL should read 0 ms, indicating the socket was reused. If they still show time, the hint did not match — check crossorigin first, then confirm the hint’s origin is byte-identical to the request’s.

Before and after, in the Timing panel The Timing panel for the same request before and after adding a matching preconnect hint. Before, DNS, initial connection and SSL each consume time before the request is even sent. After, all three read zero because the socket was already open, and the request begins with the waiting phase. before DNS 40 ms Connection 70 ms SSL 110 ms 220 ms before the request after DNS 0 ms Connection 0 ms SSL 0 ms the socket was already open Zeroes are the success signal. Any non-zero value means the hint and the request disagreed.

Consolidating Origins Beats Warming Them

preconnect reduces the cost of an origin; it does not reduce the number of origins, and the number is the multiplier. A page contacting seven vendor domains has a structural problem that no amount of hinting fixes, because you can only warm three or four before the sockets themselves become the constraint.

That makes origin consolidation the higher-leverage work, and it is more available than it looks. Vendors frequently serve the same assets from several hostnames for historical reasons, and a single configuration change on their side collapses three origins into one. A server-side container collapses many into one by construction. Self-hosting a stable vendor bundle removes an origin entirely and makes the asset eligible for your own caching rules.

Order the work accordingly: count the origins first, remove the ones you can, consolidate the ones you cannot remove, and only then warm what remains. Teams that start with the hints often end up with a page that has both a full complement of preconnects and eight third-party origins, which is the worst of both — the sockets are committed early and there are still more handshakes than sockets to cover them.

Common Pitfalls

  • Preconnecting to everything. Sockets are limited and warming one you do not use denies it to one you do. Past three or four, additional preconnects reliably cost more than they save.

  • Omitting crossorigin on a script or font origin. The warmed connection is in the wrong mode and cannot be reused, so you pay the handshake twice and the hint is worse than nothing.

  • Warming an origin the page reaches late. A connection warmed and then unused for several seconds may be closed before the request arrives. Hint origins you will contact within the first second or two.

  • Measuring on a warm connection. A second reload reports zero for every phase whether or not the hint exists, so a test that does not clear the connection pool cannot distinguish a working hint from a redundant one. Use a fresh incognito window each time.

  • Adding hints for origins the browser already knows. Your own subdomains, and any origin contacted earlier in the same navigation, are already pooled. A hint for those is inert at best.


Frequently Asked Questions

How many preconnect hints are too many?

Beyond three or four, the cost usually exceeds the benefit. Each one occupies a socket and a slice of bandwidth during the period when your own critical resources are competing for both, so a page that warms eight origins is actively slowing its own first paint to speed up requests that may never happen.

The exception is a page where you know all eight will be contacted within the first second — rare, and worth verifying from field data rather than assuming. Where you have more candidates than budget, rank by how early each origin is needed rather than by how heavy it is: the earliest request benefits most, because it has the least opportunity to overlap with anything else.

Does HTTP/3 make this unnecessary?

It reduces the cost rather than removing it. QUIC combines the transport and cryptographic handshakes, so a cold connection costs roughly two round trips instead of four, and a resumed one can approach zero. That is a genuine improvement, and it halves rather than eliminates the reason to warm connections early.

It also does not change the origin-count arithmetic at all. Five origins still cost five handshakes, just cheaper ones, so consolidating vendors onto fewer domains remains the larger lever. Check nextHopProtocol in resource timing to see which protocol you are actually getting — many vendor CDNs still negotiate HTTP/2.

Should hints go in the HTML or in a Link header?

A Link response header is marginally earlier, because the browser can act on it before the HTML body arrives — which on a slow connection is a meaningful head start. It is also harder to keep in sync with the markup, since the header is usually configured at the CDN or server layer and the scripts are in templates.

Prefer the markup unless you have measured a benefit, and generate both from the same vendor registry if you use headers. The failure mode of divergence is the one described above: a hint for an origin that is no longer used, quietly consuming a socket on every page load.

Can a service worker interfere with connection warming?

It can, in a way that is easy to misread. A service worker that answers a request from its own cache never opens a connection at all, so the warmed socket goes unused and eventually closes — which looks like a failed hint when it is actually a cache hit doing its job. Check whether the request was served by the worker before concluding the hint is broken.

The inverse also occurs: a worker that re-issues a request with different options can put it in a different connection pool from the one you warmed. Where a worker proxies third-party requests, pass the original request object through rather than constructing a new one, exactly as with priority hints.


Up: Optimizing the Network Waterfall for External Assets