Many business websites still carry speed tricks from the HTTP/1.1 era: images spread across several subdomains, one large script bundle for every page, icons packed into a sprite, small images embedded as data URIs right inside the stylesheet. Back then, each of these measures saved measurable time. Today the server speaks HTTP/2 in most cases, and the same tricks cost extra DNS lookups and connection setups, undermine prioritization and weaken the cache. This article is a rollback list: which measures from back then slow sites down today, how to spot them in the waterfall, in which order to remove them without risk – and when a page is still delivered over HTTP/1.1 after all, for example behind an old proxy.
Key takeaways
- Over HTTP/1.1, browsers today typically open 6 parallel connections (MDN Web Docs) per domain. Domain sharding multiplied that number – under HTTP/2 it only adds extra connection setups.
- HTTP/2 carries all requests of a page over one connection per host and port (IETF RFC 9113). Only within that connection can the browser prioritize files against each other in a meaningful way.
- Large bundles and sprites lose their entire cache on every change. Under HTTP/2, several smaller files that change independently of each other are usually the better choice.
- Base64 encoding makes data URIs roughly a third (MDN Web Docs) larger, and the embedded bytes can neither be cached separately nor declined by the browser.
- Server Push has been disabled since Chrome 106 (Chrome for Developers). Push directives that are still configured go nowhere; preload and 103 Early Hints take over the job.
- Every rollback starts with a cross-check: without a CDN, up to 29 percent (HTTP Archive Web Almanac 2024) of requests still used HTTP/1.1 in 2024, for example behind old proxies.
Why the old tricks were right at the time
Over HTTP/1.1, a connection can only handle one request at a time. The next file waits until the previous one has been fully transferred. Browsers work around this by opening several connections in parallel: it used to be 2 to 3 (MDN Web Docs), today 6 parallel connections (MDN Web Docs) per domain are common. For pages with many files this was a bottleneck – and that is exactly where domain sharding came in. Spreading files across three subdomains resulted in 18 connections (MDN Web Docs) instead of 6 in the MDN example, because the browser treats every subdomain as a separate domain.
The rest of that era's toolbox grew out of the same constraint. Bundles combined all scripts and stylesheets into one file each, because every request took up a place in a short waiting line. Sprites put dozens of icons into a single image that was displayed through CSS cut-outs. Data URIs embedded small images directly in the stylesheet so they needed no request of their own. And cookie-free subdomains saved the cookie header, which under HTTP/1.1 travelled uncompressed with every request. All four measures solved the same problem: too many requests for too few connections.
HTTP/2 removes that problem, and the workarounds turn into a burden. The HTTP/2 chapter of High Performance Browser Networking explicitly lists concatenated files, image sprites and domain sharding as HTTP/1.x workarounds that can be removed (High Performance Browser Networking). The rollback is therefore not an end in itself. It clears obstacles that have piled up in the frontend over the years – often without anyone on the team still knowing why they exist.
How common HTTP/1.1 still is
What HTTP/2 does fundamentally differently
HTTP/2 splits every request and response into small frames and interleaves them on a single connection. This is called multiplexing: stylesheet, script and images travel at the same time without waiting for each other. According to High Performance Browser Networking, HTTP/2 connections are therefore persistent, and only one connection per origin (High Performance Browser Networking) is required. The specification phrases it as a rule for browsers: they should not open more than one HTTP/2 connection (IETF RFC 9113) to a given host and port. Field data reflects this: at the median, a desktop page used 13 connections over HTTP/1.1 and 10 over HTTP/2 and newer (HTTP Archive Web Almanac 2024). Because every connection costs handshakes, server memory and its own congestion control, the smaller number is simply more efficient, according to the Web Almanac.
The second difference is prioritization. A protocol that sends many files over one line must be able to decide what comes first – RFC 9218 calls this ability an important feature of any implementation of a multiplexing protocol (IETF RFC 9218). The original HTTP/2 prioritization scheme did not work out: the revised specification RFC 9113 deprecates the priority signals from RFC 7540 (IETF RFC 9113, Section 5.3.2), and RFC 9218 puts a simpler scheme in their place. For the rollback, one thing matters most: prioritization only works within one connection. If the LCP image sits on img.example.com and the stylesheet on static1.example.com, two connections compete for the same bandwidth, and neither knows about the other. Hints such as fetchpriority for the LCP image then only half work.
| Measure from the HTTP/1.1 era | What it was for | What it costs under HTTP/2 | What to do instead |
|---|---|---|---|
| Domain sharding (static1, static2, img) | more parallel connections | extra DNS lookups and connection setups, separate prioritization | all files on one host, or on hosts with a shared certificate |
| One bundle for all pages | fewer requests | every change invalidates the cache of the whole file, unused code loads along | several files by change frequency and page type |
| CSS sprite | fewer image requests | all icons load, even unused ones; one change renews the whole image | individual SVG files, or SVG in the HTML for a few icons |
| Data URI in the stylesheet | no request of its own | roughly a third more bytes, lengthens the blocking stylesheet, no cache of its own | a small separate file with a long cache lifetime |
| Cookie-free subdomain | save the cookie header | its own connection; HPACK compresses repeated headers anyway | keep cookies lean, static files on the same host |
| Server Push | send files before they are requested | disabled in Chrome, push directives have no effect | preload and 103 Early Hints |
Domain sharding: more setup, less order
Under HTTP/2, the benefit of sharding turns into its opposite. According to MDN, every additional host costs its own DNS lookup and its own connection setup (MDN Web Docs), plus a TLS handshake under HTTPS. MDN therefore advises against it explicitly: under HTTP/2, domain sharding is no longer useful and is even detrimental to performance (MDN Web Docs). On top of that come the things the connections do not share. Each one builds up its congestion window from scratch, each keeps its own header compression, and the browser can only set priorities within a connection.
Browsers try to limit the damage. The technique is called connection coalescing: if two host names point to the same server address and the certificate covers both, the browser keeps using the existing connection. According to MDN, most HTTP/2 implementations use this technique to revert domain sharding (MDN Web Docs), and the Web Almanac describes how browsers often reuse one connection for two domains that share a TLS certificate (HTTP Archive Web Almanac 2024). You should still not rely on it. For HTTPS, the specification ties reuse to a certificate that is valid for the host in the URL (IETF RFC 9113, Section 9.1.1). If static1 has its own certificate or points to a different IP address, the second connection is opened anyway – and the DNS lookup happens regardless.
<!-- Before: files spread across three hosts -->
<link rel="stylesheet" href="https://static1.example.com/css/app.css">
<script src="https://static2.example.com/js/app.bundle.js" defer></script>
<img src="https://img.example.com/products/shelf.jpg" alt="Oak shelf" width="800" height="600">
<!-- After: everything from the page's own host -->
<link rel="stylesheet" href="/css/app.css">
<script src="/js/app.bundle.js" defer></script>
<img src="/images/products/shelf.jpg" alt="Oak shelf" width="800" height="600">The old subdomain addresses must not simply disappear. Images from the sharding host sit in search engine image indexes, in newsletters and on third-party pages. A permanent redirect per file to the new path keeps them reachable; the article on redirect chains as a hidden load brake explains how to avoid chains along the way.
Cookie-free domains and preconnect
preconnect and limit yourself to the few hosts needed in the visible area. The article on resource hints with preload and prefetch shows how to dose hints of this kind.Bundles: when one line clears the cache
Under HTTP/1.1, the big bundle made sense: one request for all scripts was cheaper than many individual ones. Under HTTP/2, additional requests on the same connection cost little, and the downsides of bundling come to the fore. The most important one is the cache. A file is cached as a whole and invalidated as a whole. If one line in the contact form code changes inside a large script bundle, every returning visitor downloads the complete bundle again – including the unchanged library, the menu and the slider.
The second downside is unused code. A single bundle for the whole website brings everything to every page: the configurator script to the home page, the search suggestions to the legal notice, the carousel logic to the contact page. That costs transfer, but above all processing time, because the browser has to at least read and compile any code it loads. The article on lazy loading and code splitting describes how to split JavaScript by page and need. We have examined two components that sit in many bundles in detail: the hero carousel and the site search suggestions.
The solution is not to ship every file individually. Hundreds of tiny files have costs of their own: every request carries headers, and every file gets its own compression, which works less well on very small files. A middle path based on change frequency has proven itself. Libraries that rarely change go into a separate file with a long cache lifetime. Your own code is split by page type or component. And everything gets a content hash in the file name, so that a change renews only the affected file. The page on caching strategies covers the matching cache headers.
Before: one bundle for all pages
app.bundle.js libraries, menu, slider, search, forms, configurator
-> every change renews the whole file
After: split by change frequency and page type
vendor.3f9a1c.js libraries, rarely change, long cache lifetime
base.8b21e0.js menu, consent, shared functions
home.c47d12.js home page only
search.19be77.js only on focus in the search field
form.a02f5d.js only on pages with a form
-> a change to the form renews only form.*.jsSprites and data URIs: bytes without a cache of their own
Sprites follow the same logic as bundles and share their weaknesses. The image loads all icons, including those that do not appear on the current page, and a new icon renews the entire file. There is also a problem built into raster images: a PNG sprite has to exist at double size for high-resolution screens and is then loaded on every device. Under HTTP/2, individual SVG files can be loaded in parallel at hardly any extra cost, cached individually and included as needed. Product photos and content images follow a different path anyway, described in the article on WebP and AVIF.
Data URIs are the most radical form of bundling: the image sits as Base64 text inside the stylesheet or the HTML. According to MDN, the encoding typically makes a file roughly a third (MDN Web Docs) larger than the original. What weighs more is what embedding does to loading. An image inside the stylesheet lengthens the stylesheet, and the stylesheet blocks rendering. The bytes cannot be cached separately: if one icon changes, the whole stylesheet changes. And the browser cannot decline them. High Performance Browser Networking puts it in a nutshell: inlining is equivalent to a forced push that the client can neither opt out of, cancel nor process individually (High Performance Browser Networking).
Where embedding still fits
Server Push: switched off, not just outdated
Server Push was the HTTP/2 answer to inlining: the server sends files along before the browser requests them, and the browser can decline them if it already has them in its cache. In practice, the benefit was rarely realized. According to Chrome for Developers, only 1.25 percent (Chrome for Developers) of HTTP/2 sites used the feature, and a later analysis found just 0.7 percent (Chrome for Developers). Since Chrome 106 (Chrome for Developers), Server Push has been disabled by default in Chrome and other Chromium-based browsers.
For the rollback, this means: push directives in the web server and the conversion of Link headers into push on an upstream server no longer achieve anything today. They should be removed. The Link headers themselves can stay; they still act as preload hints. Two building blocks take over the former purpose. <link rel="preload"> announces a file the browser would otherwise discover late. 103 Early Hints send these hints while the server is still computing the actual page; the article on 103 Early Hints and server think time explains how. Adoption is still low: the share only grew from 1.6 percent of desktop pages in 2022 to 2.9 percent in 2024 (HTTP Archive Web Almanac 2024).
The difference in one sentence
How to spot the legacy in the waterfall
The legacy tricks can be made visible with the browser's built-in tools. Open the developer tools, switch to the Network tab and right-click the column headers to show the “Protocol” and “Connection ID” columns. Reload the page with an empty cache. Each row now shows which protocol and which connection delivered the file. A healthy HTTP/2 page has a single connection ID for its own host; every additional ID stands for a host that has set up a connection of its own.
- Several connection IDs for hosts such as static1, static2 or img, all of which belong to your own business.
- A row's timeline shows a DNS lookup, connection setup and TLS handshake even though the page's host has long been connected.
- A single JavaScript or CSS file that is many times larger than all others and is downloaded again after every release.
- An image named sprite or icons that loads on every page, although only a few icons are visible there.
- Entries with the data: protocol, or a stylesheet whose size consists mostly of Base64 text.
- The Protocol column shows http/1.1 instead of h2 or h3 – in that case the cross-check section comes first.
For a quick overview from the command line, curl is enough. The loop below queries every host that appears in the waterfall and prints the negotiated protocol version. Send the request over HTTPS, because browsers only negotiate HTTP/2 over TLS.
# Print the protocol version per host: 2 = HTTP/2, 1.1 = no HTTP/2
for host in www.example.com static1.example.com img.example.com; do
printf '%-24s ' "$host"
curl -s -o /dev/null --http2 -w '%{http_version}\n' "https://$host/"
doneLab values are only part of the picture. Whether the rollback affects your visitors' metrics only shows up in field data; the overview page on Core Web Vitals explains how to bring the two together.
Cross-check: where HTTP/1.1 still runs
Every rollback starts with one question: does your page really reach visitors over HTTP/2? In most cases the answer is yes, in individual cases not necessarily. Via a CDN, less than 4 percent of requests used HTTP/1.1 in 2024, while without a CDN it was up to 29 percent (HTTP Archive Web Almanac 2024). Typical causes are an older load balancer or reverse proxy in front of the web server that does not offer HTTP/2, a web application firewall that terminates TLS itself, or a server configuration in which HTTP/2 is simply not switched on.
The tricky part is that the fallback happens silently. Browser and server negotiate the protocol during the TLS handshake; if the server does not offer HTTP/2, the Web Almanac describes an instant fallback to HTTP/1.1 over the same connection (HTTP Archive Web Almanac 2024). No error message, no warning – just a different entry in the Protocol column. For the rollback list, this is decisive: on an HTTP/1.1 route, domain sharding becomes the lesser evil again, and consolidating the subdomains there limits the browser to 6 connections (MDN Web Docs) for all files. Protocol first, rollback second. Bringing web servers and upstream components to HTTP/2 is part of server optimization.
// Run in the developer tools console:
// lists host and negotiated protocol per file (h2, h3, http/1.1)
// For third-party hosts the field can stay empty
const rows = performance.getEntriesByType('resource').map((e) => ({
host: new URL(e.name).host,
protocol: e.nextHopProtocol || '(empty)',
file: new URL(e.name).pathname.split('/').pop()
}));
console.table(rows);The same snippet works as a building block for your own field measurement: recording the value of nextHopProtocol for the document shows what share of visitors actually arrives over HTTP/2 or HTTP/3 – including behind corporate proxies that do not show up in the lab.
Protocol first, then rollback
Rollback in a safe order
From project work, an order has proven itself in which every step stays measurable on its own and can be reversed if needed. It is important not to change everything in one release: if you rebuild hosts, bundles and sprites at the same time, you can no longer attribute a regression to any single step.
- Secure the protocol: prove HTTP/2 for every host that appears in the waterfall – in the lab with curl, in the field with
nextHopProtocol. Only then does the rollback begin. - Remove Server Push: switch off push directives in the web server and the conversion of Link headers into push. This is low-risk, because Chrome and Chromium-based browsers no longer accept push anyway.
- Consolidate your own hosts: move static files from static1, static2 and img to the page's host, redirect the old addresses permanently per file and keep the redirects free of chains.
- Review third-party hosts: question every remaining third-party host. Whatever is needed in the visible area and cannot move gets a
preconnect; the rest loads later or not at all. - Split bundles: separate libraries from your own code, split your own code by page type, add a content hash to file names and set a long cache lifetime.
- Dissolve sprites and data URIs: deliver icons as individual SVG files, take Base64 images out of the stylesheet and keep only tiny, immediately visible icons embedded.
- Measure again: compare waterfall, number of connections and transfer size before and after each step, and watch the effect in field data over several weeks.
Fewer connections
One host for your own files means one DNS lookup, one TLS handshake and one connection on which the browser prioritizes all files together.
Targeted cache renewal
Smaller files with a content hash renew only what has changed after a release. Returning visitors download less.
Low-risk and reversible
Every step stands on its own. If a measurement shows a regression, exactly that step can be reversed without touching the others.
Many grown frontends contain measures that once solved a real problem. Removing them is no criticism of earlier work, but an adjustment to a protocol that no longer knows the old constraint.
What remains: measures that still pay off
Not everything from the HTTP/1.1 era is outdated. Compression, long cache lifetimes for versioned files, properly scaled images and dropping unused code work under any protocol. HTTP/3 does not change that either: it takes over the multiplexing of HTTP/2 and moves transport to QUIC, as the article on HTTP/3 and QUIC shows; the FAQ on the difference between HTTP/2 and HTTP/3 sums up the protocol details. The rollback list applies there just the same, because HTTP/3 also bundles the files of a host into one connection.
If you want to know which legacy tricks your own website carries, start with a technical analysis of the loading paths: waterfall, protocol per host, size and change rhythm of the bundles. The rebuild itself is handled by frontend optimization, and for grown websites with many page types there is a dedicated page on page speed for business websites. We answer questions about your own case via the contact form.
Sources and Studies
curl -s -o /dev/null --http2 -w '%{http_version}' https://www.example.com/ prints the negotiated version. For real visitors, nextHopProtocol from the Resource Timing API provides the value per file, even behind proxies that do not show up in the lab.<link rel="preload"> announces a file early that the browser would otherwise discover late. 103 Early Hints send such hints while the server is still computing the page. In both cases the browser decides for itself whether to load the file or take it from the cache – exactly what was missing with push.Related Articles
Instant Search Suggestions: Speeding Up Your Shop Search
Shop search as a real-time path: debounce input, abort outdated requests, cache frequent prefixes and render a lean suggestion list.
Hero Carousels: Why the Slider Costs You LCP
Why the image carousel at the top of the page is often the LCP element, how autoplay drives CLS and how a slider loads fast, stays stable and can be paused.
Templates, Not the Home Page: Where Load Time Is Made
Teams optimize the home page while visitors land on category, product and article pages. How to measure by template and prove the order of work.