I Cut 47 HTTP Requests to 1. The Client Thought I'd Rewritten the Whole Frontend.
A client came to me with a dashboard that felt sluggish. It wasn't — the API responses were under 100ms, the JS bundle was reasonable. But the browser was making 47 separate requests just for icons. Each one a 2KB SVG file, each one its own HTTP round trip. On a 4G connection, that's an extra 600ms of page load spent just fetching tiny icons.
I built an SVG sprite system that merged all 47 icons into one file. The page went from 47 icon requests to 1. Here's how I did it and why it matters more than you'd think.
What an SVG Sprite Actually Is
An SVG sprite is a single SVG file containing multiple <symbol> elements, each with a unique ID. Instead of loading 47 separate files, you load one sprite file once (browser caches it automatically) and reference individual icons by ID:
<svg>
<symbol id="icon-search" viewBox="0 0 24 24">
<path d="M15.5 14h-.79l-.28-.27A6.47..."/>
</symbol>
<symbol id="icon-user" viewBox="0 0 24 24">
<path d="M12 12c2.21 0 4-1.79..."/>
</symbol>
</svg>
To use an icon anywhere on the page:
<svg><use href="/sprite.svg#icon-search"></use></svg>
That's it. One HTTP request for the sprite file, cached forever, and every icon reference is instant. The <use> element clones the symbol into place without any additional network activity.
How I Built It for the Client Project
I wrote a small build script — about 30 lines — that takes a folder of SVG icons and merges them into a single sprite file. You don't need a build tool; you can do it manually if you only have 10-15 icons. For 47, automation is worth it.
The key step I almost skipped: optimizing each icon before merging. SVGO (SVG Optimizer) strips out editor metadata, comments, default values, and redundant paths. Running SVGO on the 47 source icons reduced the total sprite size from 124KB to 41KB before I even merged them. That's 67% savings from cleanup alone.
The Performance Numbers
Before: 47 icon SVG files, 94KB total (after optimization), 47 HTTP requests. Average page: 47 requests × 80ms average RTT on the client's CDN = 3.76 seconds of cumulative request overhead. Real-world impact on page load was about 600ms on 4G and 1.2 seconds on 3G.
After: 1 sprite file, 41KB, 1 HTTP request. First load: 41KB download. Subsequent pages: 0 bytes, served from browser cache. The icon portion of page load effectively disappeared after the first navigation.
The HTTP/2 argument — "multiple requests are fine now" — misses the point. Yes, multiplexing helps, but each request still has overhead. 47 requests for 47 two-kilobyte files is pathological regardless of protocol version. One 41KB request beats 47 2KB requests every time.
One Thing to Watch: The Flash of Missing Icons
If your sprite file is loaded asynchronously (which it should be), there's a moment where icons haven't rendered yet. The fix is simple: add the sprite as a hidden element at the bottom of your HTML instead of loading it as an external file. But that defeats caching across pages.
My preferred approach: inline the sprite in the initial HTML for the first page load, then use a service worker to cache it for subsequent navigations. It adds maybe 15 lines to your service worker and eliminates the flash entirely. I learned this pattern from CSS-Tricks' deep dive on SVG symbols and it's held up across three projects now.
For most projects that don't need a service worker, just load the sprite as an external file with a cache-busting query parameter (sprite.svg?v=2) and accept the first-load flash as a minor tradeoff. You can mitigate it by inlining the 3-5 most critical icons directly in the HTML and loading the rest from the sprite.