Engineers reach for the wrong resource hint constantly — sprinkling preconnect where they meant preload, or expecting fetchpriority to start a fetch it can only reorder — and the result is either wasted bandwidth on connections that never carry bytes, or a “speed-up” that speeds up nothing.
The four mechanisms in this comparison operate on three genuinely different stages of a resource’s life: opening a connection, fetching a resource, and reordering an already-discovered fetch. Conflating those stages is the single most common reason a resource hint fails to help. This guide draws the boundaries precisely, gives you a decision matrix and a decision tree, and shows how to verify the choice was correct. It sits under the broader Using Priority Hints to Control Script Execution guide, which covers the scheduling model these hints feed into.
What Each Mechanism Does at the Browser Level
Before choosing between them, you have to be exact about what each one instructs the browser to do — and, just as importantly, what it does not do.
<link rel="preconnect"> — connection warm-up, zero bytes
<link rel="preconnect" href="https://cdn.analytics-provider.com" crossorigin>
A preconnect tells the browser to perform the full connection handshake to an origin ahead of time: DNS resolution, the TCP three-way handshake, and — for HTTPS — the TLS negotiation. It downloads no resource bytes. When a later request to that origin is made, the socket is already open and warm, saving anywhere from 100 ms to 500 ms of round-trip latency depending on the user’s RTT and whether TLS 1.3 or an older handshake is in play.
The crossorigin attribute matters here: a bare preconnect to a cross-origin host warms only the unauthenticated connection pool. Scripts and fonts are fetched over CORS-mode (anonymous) connections, so for a script origin you almost always want crossorigin on the preconnect, otherwise the browser opens a second, separate connection at fetch time and the warm-up was wasted.
Critically, preconnect does not need to know the resource URL — only the origin. That is exactly what makes it the right tool when a fetch is coming soon but the precise path is decided at runtime (a common shape for consent-gated vendor SDKs).
<link rel="dns-prefetch"> — DNS resolution only
<link rel="dns-prefetch" href="https://cdn.analytics-provider.com">
dns-prefetch is the lighter sibling of preconnect: it resolves the origin’s DNS name to an IP address and stops there. No socket, no TLS. It is dramatically cheaper (a single DNS query, often already cached by the resolver) and has near-zero downside, which makes it a safe fallback and a good hint for origins you might contact — or for a broad set of many origins where opening a full connection to each would be wasteful. On modern connections DNS is rarely the bottleneck, so dns-prefetch alone typically saves only 20–120 ms.
<link rel="preload" as="script"> — early, high-priority fetch of a known URL
<link rel="preload" href="https://cdn.analytics-provider.com/v2/core.js" as="script" crossorigin>
preload is the only mechanism in this set that actually fetches bytes. It instructs the browser to download a specific, known URL early and at high priority, storing it in the memory cache so that when the real <script> reaches the parser (or is injected later), the response is already available and execution starts immediately. The as="script" attribute is mandatory — it tells the browser the destination so it applies the correct priority, Accept header, and CSP directive.
Two attributes must line up or the preload is silently wasted: the href must match the eventual request URL exactly, and crossorigin on the preload must match the CORS mode of the actual fetch. A cross-origin script fetched anonymously needs crossorigin on the preload; omit it (or add credentials that don’t match) and the browser fetches the resource twice — once for the unmatched preload, once for the real script. This exact-match requirement is covered in depth in Implementing Preload and Prefetch for Third-Party Scripts.
The fetchpriority attribute — a scheduling nudge, not a fetch trigger
<script src="https://cdn.analytics-provider.com/v2/core.js" fetchpriority="high" async></script>
fetchpriority is an attribute on an already-discovered fetch — a <script>, <img>, <link rel="preload">, or a fetch() call — that adjusts where that request sits in the browser’s priority queue. It takes high, low, or auto (the default heuristic). It does not initiate a fetch and it does not open a connection; it only reorders a request the browser already knows about, relative to its peers. On HTTP/2 and HTTP/3 the value is also mapped onto stream priority signals so the server can interleave frames accordingly.
The mental model that keeps these straight: preconnect opens the pipe, preload pulls the bytes through it early, and fetchpriority decides who goes first when several requests are queued. dns-prefetch is a cheaper preconnect that only does the address lookup. None of them are interchangeable, because each acts on a different stage.
Decision Matrix: Which Hint for Which Job
| Mechanism | What it does | Network cost | When to use for third-party scripts | Common misuse |
|---|---|---|---|---|
preconnect |
DNS + TCP + TLS handshake; opens a warm socket. No bytes. | One full handshake per origin (sockets are a finite resource). | You’ll fetch from a cross-origin script host soon but the exact URL is decided at runtime (e.g. a consent-gated vendor SDK). | Preconnecting to 6+ origins “just in case,” saturating the connection pool and delaying critical fetches. Also: omitting crossorigin for a script/font origin. |
dns-prefetch |
DNS resolution only. No socket, no bytes. | A single DNS query; effectively free. | A long list of possible origins, or a low-confidence hint where a full preconnect would waste a socket. Safe fallback for older browsers. |
Expecting it to save handshake time — it only removes DNS latency (often already cached). Using it where a real fetch needs preload. |
preload |
Fetches a specific known URL early, high priority, into cache. | Full transfer size of the resource, spent up front. | A known, critical script URL you want in cache before the parser or injector reaches it (analytics core, CMP stub). | Preloading a URL that never gets requested (the “unused preload” warning), or a crossorigin mismatch causing a double fetch. |
fetchpriority |
Reorders an already-discovered fetch (high/low/auto). |
None — it moves an existing request in the queue. | You already have a <script> or preload in flight and need it to win (or yield) priority against peers. |
Expecting it to start a fetch. Marking every script high, which flattens priority so nothing is prioritized. |
The rows are ordered by how early in the resource lifecycle they act — connection, then fetch, then reordering. Read the table that way and the choice usually becomes obvious: identify which stage your problem lives in, and only one column will actually address it.
Decision Tree: Picking the Right Hint
The tree below encodes the fast path. Start at the top with the question “do I even know the URL yet?” — that single distinction routes most decisions.
In practice these hints layer: a well-tuned third-party script might get a preconnect to its CDN at the top of <head>, a preload for its known core bundle, and fetchpriority="high" on the eventual <script> element. They are complementary because they act on different stages — not alternatives. The full network sequencing this feeds into is the subject of Optimizing the Network Waterfall for External Assets, which shows how connection warm-up and early fetches reshape the critical path.
Verification
A resource hint you can’t measure is a resource hint you can’t trust. Confirm each one did what you intended:
Common Pitfalls
- Preconnecting to too many origins. Each
preconnectconsumes a socket and competes with critical fetches for the connection pool and bandwidth. More than three or four preconnects usually costs more than it saves. Reservepreconnectfor the one or two highest-value cross-origin script hosts, and downgrade the rest todns-prefetch. crossoriginmismatch. The most common silent failure. Scripts and fonts are fetched anonymously (CORS mode), so apreconnectorpreloadfor them needscrossorigin. Omit it — or add credentials that don’t match the real request — and the browser opens a second connection or issues a second fetch, doubling the cost you were trying to cut. The preload/prefetch matching rules are detailed in Implementing Preload and Prefetch for Third-Party Scripts.preloadwith no matching request. Apreloadwhose URL is never actually fetched wastes bandwidth on a resource the page doesn’t use and triggers the “unused preload” console warning. Only preload URLs you are certain will be requested this navigation; if the fetch is conditional (behind consent, say), gate the preload behind the same condition.- Expecting
fetchpriorityto fetch.fetchpriorityon an element that doesn’t already trigger a fetch does nothing — it is a reordering hint for a request the browser has already discovered. To cause an early fetch you needpreload(or a statically parsed<script>); to reorder one that already exists, usefetchpriority. The scheduling model behind the attribute is covered in Using Priority Hints to Control Script Execution.
Related
- Using Priority Hints to Control Script Execution — the scheduling model and
fetchprioritysemantics this decision feeds into - Implementing Preload and Prefetch for Third-Party Scripts — exact-URL and
crossoriginmatching rules forpreload - Optimizing the Network Waterfall for External Assets — how connection warm-up and early fetches reshape the critical path
- Implementing fetchpriority=high for Critical Analytics — applying the reordering hint to a dynamically injected analytics script