1. Twenty-Three Origins
I profiled a fashion storefront last year and counted the distinct hostnames its product page contacted. Twenty-three. Not twenty-three requests — twenty-three separate origins, each requiring its own DNS lookup, its own TCP handshake, its own TLS negotiation before a single useful byte moved.
The merchant knew about maybe eight of them. The rest arrived indirectly: a tag manager loading an analytics vendor that loaded a data broker, a review widget pulling its own fonts from a CDN, a personalisation tool contacting three separate endpoints for configuration, experiments, and telemetry.
On the office wifi this was invisible. On a 4G connection in a car park, each new origin cost roughly 300 milliseconds before it delivered anything, and several of them were being contacted during the page's most sensitive moments.
This article is about that cost — what a connection actually consists of, what it's worth to open one early with preconnect and dns-prefetch, and the more uncomfortable conclusion that arrives once you've measured it properly: the hints help a bit, and having fewer origins helps enormously.
2. What a Connection Actually Costs
Before the browser can ask a new host for a file, three separate negotiations must complete in sequence. Each is at least one round trip, and on mobile a round trip is expensive.
DNS resolution. Turning cdn.vendor.example into an IP address. If the answer is cached locally, free. If not, a query to the resolver, which may itself have to walk up the hierarchy. Typically 20–120ms, occasionally far worse on a congested mobile network or a poorly configured corporate resolver. CNAME chains make it worse: some CDN and tag vendor hostnames resolve through three or four CNAMEs, each potentially a fresh lookup.
TCP handshake. The SYN, SYN-ACK, ACK exchange. Exactly one round trip, so it costs whatever your round-trip time is. On fibre, 10ms. On 4G, 50–100ms. On a busy 3G connection or a rural mobile signal, 200ms and up.
TLS negotiation. One additional round trip with TLS 1.3, two with TLS 1.2. Session resumption can reduce this to zero round trips, but only for a host the browser has spoken to recently.
Add them up for a first contact with a new origin on a connection with 100ms RTT and you get something like 300ms of pure protocol overhead before the request is even sent. Then the request goes out, the server thinks, and the response comes back — that's the part people measure and the part that's usually smaller.
Three origins on the critical path, contacted in sequence, and you have spent nearly a second on handshakes.
Why mobile is worse than the numbers suggest
Round-trip time on a mobile network isn't a fixed property, it's a function of whether the radio is awake. A phone whose radio has gone idle needs to renegotiate with the tower before it can send anything, which adds a variable and occasionally large delay to the first packet of a new connection. Subsequent packets on an established connection don't pay it.
This is why the gap between lab and field data is so wide for connection-heavy pages, and why a page that tests fine on a throttled desktop connection can feel sluggish on a real phone. Network throttling in DevTools simulates bandwidth and latency; it doesn't simulate a radio waking up.
3. dns-prefetch
The cheapest hint available. It performs the DNS lookup and stops there.
<link rel="dns-prefetch" href="https://reviews.vendor.example" />
No connection is opened, no TLS is negotiated, and nothing is held. The cost is one DNS query, which is small enough that being wrong about it barely matters. That makes dns-prefetch the right tool for origins you'll probably use but can't be sure about — a chat widget that only loads for logged-in customers, a payment provider you'll only contact if the visitor reaches checkout.
It's also the more broadly-supported of the two, which is why you still see the belt-and-braces pattern of specifying both. That pattern made more sense a decade ago; today, if an origin is worth a hint, it's usually worth a preconnect, and adding dns-prefetch alongside is harmless but rarely does anything.
The saving is real but modest: you're removing one leg of three. Useful when it's free, not transformative.
4. preconnect
Does the whole thing — DNS, TCP, and TLS — and holds the connection open, ready.
<link rel="preconnect" href="https://cdn.shop.example.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
When it works, it removes the entire 300ms from the critical path, because the handshake happens in parallel with whatever else the browser is doing rather than in front of the request that needs it. That's a large win for one line of markup, and it's why preconnect is worth understanding properly.
The crossorigin trap
This one catches nearly everybody, so it's worth being slow about.
A connection is keyed not just by origin but by credentials mode. A request made in anonymous CORS mode — which is how fonts are fetched, always — cannot reuse a connection that was opened in credentialed mode. So a preconnect without crossorigin, followed by a font request, opens two connections: the speculative one you asked for, which goes unused, and the real one, which pays full price.
You have made the page slower and added a connection.
The rule: crossorigin on any preconnect for fonts, and on any preconnect for a resource fetched with CORS. No crossorigin for ordinary same-credentials requests like images or scripts from your CDN. When genuinely unsure, check the Network panel — if you see two connections to the same host in the Connection ID column, this is why.
The canonical example, which is copied wrongly across the web more often than rightly:
<!-- Correct: the stylesheet host is credentialed, the font host is not -->
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
Connections expire
A preconnected socket that goes unused is closed after a short idle period — around ten seconds in Chrome. Preconnect to an origin you won't contact for fifteen seconds and the connection has already been reclaimed; you've paid for the handshake and thrown it away.
Practically, this means preconnect is for origins used early. For something used late — a chat widget that appears after thirty seconds, an analytics beacon that fires on exit — the hint is wasted, and dns-prefetch is the better choice because DNS results are cached for much longer.
5. When Connections Get Reused, and When They Don't
Not every hostname costs a connection. Browsers reuse connections more cleverly than most people assume, and knowing the rules stops you hinting for savings that already exist.
Connection coalescing. Over HTTP/2 and HTTP/3, a browser may serve requests for two different hostnames over a single connection, provided both resolve to the same IP address and the certificate covers both names. So img.shop.example.com and static.shop.example.com, both behind the same CDN with a wildcard certificate, can share one connection and one handshake.
This quietly undoes the old domain-sharding optimisation. Under HTTP/1.1, splitting assets across four hostnames bought you more parallel requests, because browsers limited connections per host. Under HTTP/2 that limit is irrelevant — one connection multiplexes everything — and sharding now costs handshakes for no benefit. If your site still shards assets across static1, static2, static3, that's a migration artefact worth removing.
Socket pools have limits. Browsers cap total simultaneous connections, historically around six per host under HTTP/1.1 and a smaller total budget across all hosts. Speculative connections from your hints draw on the same pool. Ask for too many and you're not just wasting sockets, you're potentially delaying real requests waiting for one.
Cache partitioning changed the maths. This deserves emphasis because it invalidated a decade of received wisdom. Browsers now key the HTTP cache by the top-level site, not just the resource URL. A library fetched from a public CDN on another site is not available to yours; your visitor downloads it again.
Every argument that began "use a public CDN so visitors already have it cached" is now false. That covers font CDNs, jQuery from a public host, and shared framework bundles. The change was made deliberately, to close a privacy side channel where sites could infer browsing history from cache timing. It is not coming back.
What survives is the genuine CDN argument: geographic proximity and good peering. That's real, and it's an argument for your assets being on a CDN, not for them being on someone else's shared one.
Preconnect doesn't survive navigation. Connections don't carry across a page load in any way you can rely on, so hinting on page A for something used on page B doesn't work. That's what prefetch and Speculation Rules are for.
6. preconnect Is Not preload
These get confused constantly, and the confusion produces some strange configurations.
preconnect opens a road. It says: I'm going to talk to this host, get the pipe ready. It does not fetch anything and it doesn't need to know what you'll ask for.
preload fetches a specific file. It says: download this exact resource now, at high priority. It needs the full URL.
If you are also weighing whether to deliver these hints in a 103 Early Hints response rather than in markup, that is a separate decision and worth reading on its own. Use preconnect when you know the host but not the file — which is the normal case for third parties, because the URL of the resource is decided by a script that hasn't run yet. Use preload when you know the exact file and it's genuinely critical.
Doing both for the same resource is redundant: a preload opens the connection as a side effect of fetching. If you find both in a template, remove the preconnect.
7. You Cannot Hint Your Way Out of This
Here is where most articles on this topic stop, and where the interesting part begins.
Preconnecting is not free. Each speculative connection consumes a socket, holds server resources, and on mobile keeps the radio awake — which costs battery and, on some networks, adds contention. Browsers know this and will quietly ignore you if you overdo it.
The practical ceiling is around four to six preconnects, and honestly if you need six you have a different problem. Beyond that you're not prioritising, you're just opening connections.
So with twenty-three origins and a budget of four hints, the arithmetic doesn't work. Nineteen origins still pay full price. The hints are treating a symptom of a page that talks to too many hosts, and the actual fix is to talk to fewer.
That reframing matters because it changes what you do on Monday. Hints are a half-hour task with a modest payoff. Reducing origin count is a longer project with a much larger one, and it improves privacy, reliability, and PCI posture at the same time.
8. Auditing What You Actually Contact
Start by counting. This is more revealing than any synthetic test:
// Every distinct origin this page contacted, with request counts and total time
const byOrigin = {};
for (const e of performance.getEntriesByType('resource')) {
const host = new URL(e.name).origin;
byOrigin[host] ??= { requests: 0, bytes: 0, connectMs: 0 };
byOrigin[host].requests++;
byOrigin[host].bytes += e.transferSize || 0;
// Connection setup only happens on the first request to a host
if (e.connectEnd > e.connectStart) {
byOrigin[host].connectMs += Math.round(e.connectEnd - e.domainLookupStart);
}
}
console.table(
Object.entries(byOrigin)
.map(([origin, v]) => ({ origin, ...v, kb: Math.round(v.bytes / 1024) }))
.sort((a, b) => b.connectMs - a.connectMs)
);
Sort by connection time and the top of that table is your hint list. But read the whole table, because the useful question isn't "which origins should I preconnect to" — it's "why am I contacting twenty-three hosts."
For each one, three questions. Who owns it? What breaks if it's removed? Is it on the critical path or can it wait?
In my experience, the answers cluster: a third are essential and early, a third are essential but could be deferred, and a third nobody can justify. That last group is where the wins are, and they're free.
9. Fewer Origins
Four techniques, in rough order of effort against payoff.
Self-host your fonts
The best single change available to most sites, and the reasoning has changed in a way many teams haven't caught up with.
The historical argument for a font CDN was shared caching: if a visitor had already downloaded a font from Google Fonts on another site, yours would load instantly from cache. That argument is dead. Browsers partitioned their HTTP cache by top-level site — Chrome did it in 2020, and Safari and Firefox have equivalent protections — specifically to close the privacy leak that shared caching enabled. A font fetched on another site is no longer available to yours.
So you're now paying for two extra origins (fonts.googleapis.com for the CSS, fonts.gstatic.com for the files), a chained dependency where the CSS must arrive before the font is discovered, and a third party in your critical path, in exchange for a cache benefit that no longer exists.
Self-hosting removes all of it. The font is on an origin you've already connected to, discovered without a round trip to another host, under your own cache headers.
@font-face {
font-family: 'Space Grotesk';
src: url('/fonts/space-grotesk-400.woff2') format('woff2');
font-weight: 400;
font-display: swap;
/* Only download the glyphs you actually render */
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+2000-206F, U+2074, U+20AC;
}
While you're there, count your weights. Most sites ship four to six and use two. Each is a separate file on the critical path.
Proxy third parties through your own origin
For scripts that don't need to be on the vendor's host, serving them from your domain removes an origin entirely and lets them reuse a connection you've already opened.
Be honest about the trade-off, though. You take on responsibility for updates, and you lose the vendor's ability to ship an urgent fix. For an analytics library, that's usually fine. For a payment SDK it is not — and providers like Stripe explicitly require you to load their script from their domain, for good reasons about their own ability to respond to incidents.
Move tags server-side
Server-side tagging replaces a fan-out of browser connections with one connection to an endpoint you control, with the distribution happening server-to-server. For a page carrying six analytics and advertising tags, this can remove five origins from the browser's work.
It's a real project, not an afternoon, and it changes how your measurement works. But the performance win is larger than every hint in this article combined, and it improves data quality against ad blockers as a side effect.
Delete things
Unglamorous and consistently the highest return. The A/B testing tool from a cancelled programme. The heatmap nobody has opened since the redesign. The second analytics platform kept "for comparison" since 2021. The social sharing widget with a 0.02% click rate that contacts four origins.
Every one of those is DNS plus TCP plus TLS plus bytes plus main-thread execution, on every page load, for every customer. Removing them requires no engineering skill and no hints.
10. Fonts: The Origin Problem and the Rest of It
Fonts are the most common reason to reach for these hints, so it's worth going past the connection and covering what else goes wrong — because self-hosting solves the origin problem and leaves three others intact.
The discovery chain
A font is referenced inside a @font-face rule inside a stylesheet. The browser must fetch the CSS, parse it, work out that some element on the page uses that family, and only then request the font. That's a sequential chain, and it's why fonts arrive late even on well-built pages.
preload is the fix, because it's the one case where you reliably know something the preload scanner cannot:
<link rel="preload" href="/fonts/space-grotesk-400.woff2" as="font" type="font/woff2" crossorigin />
Preload only the weights actually used above the fold — usually one, occasionally two. Preloading six fonts means six high-priority requests competing with your CSS and your hero image, and the result is that everything arrives late together.
font-display, and choosing your failure mode
Until the font arrives, the browser must decide what to show. font-display is where you choose.
swap renders fallback text immediately and swaps when the font loads. Text is readable at once; the swap causes a visible reflow that can hurt Cumulative Layout Shift. optional gives the font a very short window and otherwise sticks with the fallback for that page view — best for CLS, and means some visitors never see your typeface. block hides text briefly and is almost always the wrong choice on a storefront, because invisible product names are worse than approximate ones.
I default to swap for body text and consider optional where brand typography is decorative. The right answer depends on whether an unstyled first paint or a late shift is more damaging for your page, which is a design judgement rather than a technical one.
Making the swap invisible
The layout shift on swap comes from the fallback font having different metrics. Modern CSS lets you tune a fallback to match, so the swap barely moves anything:
@font-face {
font-family: 'Space Grotesk Fallback';
src: local('Arial');
size-adjust: 105%;
ascent-override: 92%;
descent-override: 24%;
line-gap-override: 0%;
}
body {
font-family: 'Space Grotesk', 'Space Grotesk Fallback', sans-serif;
}
Getting the numbers right is fiddly — there are tools that compute them by comparing metrics — but the payoff is real: most of the CLS attributed to web fonts disappears, and you keep swap's fast first paint.
Subset, and count your weights
A full Latin-Extended font with every glyph is several times the size of one subset to the characters you render. unicode-range lets the browser skip files whose ranges aren't used on the page.
And count the weights. I have yet to audit a storefront that used every weight it loaded. Four weights at two styles is eight files; the design usually needs three. Variable fonts can help — one file covering a continuous weight range — but only if you genuinely use several weights, since a variable font is larger than a single static instance.
11. Where the Hints Genuinely Earn Their Place
Having argued for fewer origins, let me be clear that hints are still worth adding for the ones that survive.
Your own CDN or image host, if assets are served from a different hostname than the document. This is the highest-value preconnect on most storefronts, because it's used immediately and heavily.
Your payment provider, hinted from the cart page rather than site-wide. The connection is then open when the customer reaches checkout, and a stall during payment entry is the most expensive stall on the site.
<!-- On cart, in anticipation of checkout -->
<link rel="preconnect" href="https://js.stripe.com" />
<link rel="dns-prefetch" href="https://api.stripe.com" />
Your search provider, hinted from any page with a search box, because customers who search tend to search early.
A font host, if you haven't self-hosted yet — with crossorigin.
Notice the pattern: hints scoped to the templates where the origin is used soon, rather than dumped into a global header. A site-wide preconnect to your payment provider fires on every blog post and product listing, opening connections that idle out unused. Scope them.
12. Deferring What You Cannot Delete
Some origins survive the audit because the business genuinely needs them, but need doesn't mean urgent. A tag that must exist does not have to exist during the first two seconds, and moving work off the critical path is often easier to get agreed than removing it — nobody has to give anything up.
The crudest version, which is fine for most marketing tags:
<script>
// Load non-essential third parties once the page is usable
function loadDeferred() {
['https://reviews.vendor.example/widget.js',
'https://chat.vendor.example/loader.js'].forEach(src => {
const s = document.createElement('script');
s.src = src;
s.async = true;
document.head.appendChild(s);
});
}
if (document.readyState === 'complete') {
requestIdleCallback ? requestIdleCallback(loadDeferred, { timeout: 4000 }) : setTimeout(loadDeferred, 2500);
} else {
window.addEventListener('load', () => {
requestIdleCallback ? requestIdleCallback(loadDeferred, { timeout: 4000 }) : setTimeout(loadDeferred, 2500);
}, { once: true });
}
</script>
A more targeted version loads a widget only when the customer is about to see it, which is the right pattern for anything below the fold:
// Load the reviews widget when its container approaches the viewport
const slot = document.querySelector('#reviews');
if (slot) {
new IntersectionObserver((entries, obs) => {
if (!entries.some(e => e.isIntersecting)) return;
obs.disconnect();
const s = document.createElement('script');
s.src = 'https://reviews.vendor.example/widget.js';
s.async = true;
document.head.appendChild(s);
}, { rootMargin: '400px' }).observe(slot);
}
The rootMargin gives the widget a head start so it's ready by the time it scrolls in, which is the difference between deferring and simply making it late.
Two cautions. Deferring an analytics tag means losing the sessions that bounce before it fires, and that shows up as a traffic drop somebody will notice — agree it in advance rather than explaining it afterwards. And anything that affects layout must not be deferred without a reserved space, or you have traded a slow page for a shifting one, which is worse.
Consent-gated tags are already deferred whether you planned it or not, since they cannot load until the banner is answered. That's worth remembering when you look at your waterfall: the version you see with consent granted is not the version most first-time visitors experience.
13. Measuring Whether It Worked
Connection timing is exposed per-resource, so you can measure this directly rather than inferring it:
// Connection setup cost per origin — zero means a connection was reused
const first = {};
for (const e of performance.getEntriesByType('resource')) {
const host = new URL(e.name).origin;
if (first[host]) continue;
first[host] = {
dns: +(e.domainLookupEnd - e.domainLookupStart).toFixed(1),
tcp: +(e.connectEnd - e.connectStart).toFixed(1),
tls: e.secureConnectionStart ? +(e.connectEnd - e.secureConnectionStart).toFixed(1) : 0,
startedAt: Math.round(e.startTime)
};
}
console.table(first);
After adding a preconnect, the DNS, TCP and TLS figures for that origin should collapse to roughly zero on the resource that uses it, because the work happened earlier on a different timeline. If they don't, the hint isn't taking effect — the usual causes being a crossorigin mismatch, the connection idling out before use, or the browser declining because you asked for too many.
Then confirm it in the metric that matters. Connection savings show up in LCP when the connected origin serves something on the critical path, and in nothing at all when it doesn't. A preconnect that improves your waterfall aesthetics but not your LCP has cost you a socket for no user benefit.
Use field data rather than lab runs for the final judgement. Connection cost varies enormously with network conditions, and the lab does not contain the customer on a train.
14. What This Looks Like on a Storefront
Homepage. Usually the worst offender for origin count, because it accumulates marketing tags. Preconnect to the image CDN and the font host if you have one. Audit everything else.
Category pages. Image CDN, and your search or merchandising provider if faceting is served externally. Faceted navigation that round-trips to a third party on every filter click is worth hinting and worth questioning.
Product pages. Image CDN above all. Review platforms are the common second origin, and they're a good candidate for dns-prefetch rather than preconnect, since reviews usually render below the fold and load late.
Cart and checkout. Payment provider, fraud tooling if it's on a separate origin, and nothing else if you can help it. This is the page to be ruthless about: every third party here is both a performance cost and a PCI scope question.
15. Why Twenty-Three Happened
No one decided to contact twenty-three hosts. It accumulated, one reasonable request at a time, and understanding the mechanism is how you stop it recurring after you've cleaned up.
The pattern is always similar. A tag is added for a campaign with a clear owner and a clear purpose. The campaign ends. The owner moves teams. The tag stays, because removing it requires someone to be confident it's unused, and nobody is ever confident. Multiply by four years.
Tag managers accelerate this by design — that's their purpose, to let non-engineers add tracking without a deploy. The cost is that the people adding scripts are not the people who own page performance, and the feedback loop between the two is usually nonexistent.
Three things that actually work against it:
An owner and an expiry on every tag. When a tag is added, record who asked for it and when it should be reviewed. Six months is reasonable. At review, the owner either renews it or it goes. This is a process change rather than a technical one and it's the only thing I've seen genuinely hold the line.
A budget with a number in it. "Keep the site fast" is unenforceable. "No more than eight origins on the critical path of a product page, and no third-party script above 40KB compressed" is a rule that can be checked and that gives someone grounds to say no. Put it in CI if your tooling allows:
// A crude but effective budget check for a synthetic run
const origins = new Set(
performance.getEntriesByType('resource')
.filter(e => e.startTime < 3000)
.map(e => new URL(e.name).origin)
);
if (origins.size > 8) {
throw new Error(`origin budget exceeded: ${origins.size} — ${[...origins].join(', ')}`);
}
Make the cost visible to the people adding tags. A dashboard showing origin count and LCP by template, shared with marketing rather than kept in engineering, changes the conversation from "engineering says no" to a shared number that everyone can see moving. The retailer in the case study put theirs on a wall display; tag requests dropped noticeably without anyone issuing a policy.
None of this is exciting, and all of it outlasts a one-off cleanup. A site that gets audited and cleaned but keeps the same intake process is back to twenty-three origins in three years. I have seen the same site twice.
16. A Worked Example
Returning to the twenty-three-origin fashion retailer, because the numbers are instructive.
The audit. Of twenty-three origins, seven were contacted before the largest contentful paint. Google Fonts accounted for two, the image CDN one, and the remaining four were a tag manager and three tags it loaded synchronously.
What we removed. Six origins went entirely: two dead A/B testing endpoints, a social widget, a duplicate analytics install, and two hosts belonging to a personalisation tool that had been switched off in the admin but whose script was still in the theme. Nobody could name an owner for any of them. That took one deploy.
Fonts. Self-hosted, cut from five weights to two, subset to Latin. Removed two origins and roughly 90KB. This was the largest single improvement and took most of a day, nearly all of it spent confirming which weights the design actually used.
Tags. Three moved behind a server-side container. Two more were deferred to after the load event, which doesn't remove the origin but takes it off the critical path.
Hints. Then, and only then, three preconnects: the image CDN, the payment provider from the cart template, and the search provider. Four lines of markup.
Result. Origins on the critical path went from seven to two. Mobile LCP improved by 1.1 seconds, of which the hints accounted for roughly 200ms and the deletions the rest. Total bytes on first load dropped by about a third.
The honest read. If we had only added the hints — the thing the original ticket asked for — we'd have got the 200ms and declared it done. The ticket was for the wrong work, and the audit is what revealed it. Whenever someone asks me to add resource hints now, I ask to see the origin table first.
17. Questions People Ask
"How many preconnects can I have?" Four to six before browsers start ignoring you and the costs outweigh the gains. If your list is longer, the list is the problem.
"Should I preconnect to my own domain?" No. The connection that fetched your HTML is already open and will be reused. This appears in a surprising number of templates and does nothing.
"Does preconnect help HTTP/3?" Yes, though less dramatically. QUIC combines transport and cryptographic setup into fewer round trips, and can resume at zero, so there's less to save — but the DNS lookup and initial handshake still exist, and hinting still removes them from the critical path.
"Why does DevTools show two connections to one host?" Almost always the crossorigin mismatch. One connection from your credentialed preconnect, one from the anonymous font request that couldn't reuse it.
"We added preconnects and nothing changed." Either the origins weren't on the critical path, or your bottleneck is elsewhere. Connection hints only help when a handshake is blocking something a user is waiting for. If your LCP is slow because of a render-blocking script or a 900ms TTFB, no amount of connection warming will help.
"Is it worth hinting an origin used once, for a small file?" Usually not. The handshake cost is per-origin regardless of file size, so a single small request to a distant host is expensive relative to its value — which is a good argument for moving that file to an origin you already use rather than hinting it.
"Does the order of hints in the head matter?" Yes, more than people expect. Hints are acted on in document order, and they compete with each other and with everything else the parser discovers. Put them near the top of <head>, above your stylesheets, and order them by how early the origin is actually needed. A preconnect sitting below a render-blocking stylesheet has already lost most of its value.
"Should I hint from HTTP headers instead of markup?" You can — Link: <https://cdn.example.com>; rel=preconnect as a response header does the same job and arrives fractionally earlier, since the browser sees it before parsing any HTML. It also composes with Early Hints, where header-based links are the only option. The trade-off is that headers are set in infrastructure and markup is set in templates, so header hints are easier to forget about and harder for a front-end developer to change. I use markup by default and headers when the timing genuinely matters.
"Our CDN already does this automatically." Several CDNs offer automatic hint injection based on observed traffic. It's reasonable and it is not a substitute for the audit — automation optimises the page you have, including the six origins nobody needs. Check what it's actually emitting, because I have seen automated preconnects to origins that had been removed from the site months earlier.
"Can these hints hurt privacy?" Worth thinking about. A dns-prefetch or preconnect to a vendor happens before any consent decision, and it tells that vendor's infrastructure that someone is on your page. If you're operating under GDPR with a consent banner blocking tags, hinting those tags' origins somewhat undermines the point. Hint the origins you use unconditionally; leave the consent-gated ones alone.
18. The Order I'd Work In
Run the origin table on your busiest template. Not a synthetic test — a real page, in a real browser, with your real tag stack.
Read it and be uncomfortable about the number. Then, for each origin, find an owner. The ones with no owner go on a deletion list, and you should expect that list to be longer than you'd like.
Self-host your fonts. The shared-cache argument that justified a font CDN stopped being true years ago, and most sites are carrying two extra origins and a chained dependency out of habit.
Look at what's left and ask which of it could be deferred, moved server-side, or served from your own domain.
Then add three or four preconnects for what remains on the critical path, scoped to the templates that need them, with crossorigin where it belongs.
One caveat on sequencing: don't hold the hints hostage to the cleanup. If adding three preconnects takes twenty minutes and the origin audit will take a quarter to work through politically, ship the hints now and start the longer conversation in parallel. The mistake is not doing the small thing first — it's doing the small thing, seeing the number improve slightly, and concluding the problem is handled.
The hints are the last step and the smallest one. They're also the step everyone starts with, because it's a line of markup rather than a conversation about why the marketing team has three analytics platforms. That conversation is the actual work, and the origin table is how you start it — twenty-three rows on a screen is a much better argument than an opinion about page weight.