I wrote that using inline <svg>
icons make for the best icon system. I still think that’s true. It’s the easiest possible way to drop an icon onto a page. No network request, perfectly styleable.
But inlining code has some drawbacks, one of which is that it doesn’t take advantage of caching. You’re making the browser read and process the same code over and over as you browse around. Not that big of a deal. There are much bigger performance fish to fry, right? But it’s still fun to think about more efficient patterns.
Scott Jehl wrote that just because you inline something doesn’t mean you can’t cache it. Let’s see if Scott’s idea can extend to SVG icons.
Starting with inline SVG
Like this…
<!DOCTYPE html>
<html lang="en">
<head>
<title>Inline SVG</title>
<link rel="stylesheet" href="/styles/style.css">
</head>
<body>
...
<svg width="24" height="24" viewBox="0 0 24 24" class="icon icon-alarm" xmlns="http://www.w3.org/2000/svg">
<path id="icon-alarm" d="M11.5,22C11.64,22 11.77,22 11.9,21.96C12.55,21.82 13.09,21.38 13.34,20.78C13.44,20.54 13.5,20.27 13.5,20H9.5A2,2 0 0,0 11.5,22M18,10.5C18,7.43 15.86,4.86 13,4.18V3.5A1.5,1.5 0 0,0 11.5,2A1.5,1.5 0 0,0 10,3.5V4.18C7.13,4.86 5,7.43 5,10.5V16L3,18V19H20V18L18,16M19.97,10H21.97C21.82,6.79 20.24,3.97 17.85,2.15L16.42,3.58C18.46,5 19.82,7.35 19.97,10M6.58,3.58L5.15,2.15C2.76,3.97 1.18,6.79 1,10H3C3.18,7.35 4.54,5 6.58,3.58Z"></path>
</svg>
It’s weirdly easy to toss text into browser cache as a file
In the above HTML, the selector .icon-alarm
will fetch us the entire chunk of SVG for that icon.
const iconHTML = document.querySelector(".icon-alarm").outerHTML;
Then we can plunk it into the browser’s cache like this:
if ("caches" in window) {
caches.open('static').then(function(cache) {
cache.put("/icons/icon-wheelchair.svg", new Response(
iconHTML,
{ headers: {'Content-Type': 'image/svg+xml'} }
));
}
}
See the file path /icons/icon-wheelchair.svg
? That’s kinda just made up. But it really will be put in the cache at that location.

Let’s make sure the browser grabs that file out of the cache when it’s requested
We’ll register a Service Worker on our pages:
if (navigator.serviceWorker) {
navigator.serviceWorker.register('/sw.js', {
scope: '/'
});
}
The service worker itself will be quite small, just a cache matcher:
self.addEventListener("fetch", event => {
let request = event.request;
event.respondWith(
caches.match(request).then(response => {
return response || fetch(request);
})
);
});
But… we never request that file, because our icons are inline.
True. But what if other pages benefitted from that cache? For example, an SVG icon could be placed on the page like this:
<svg class="icon">
<use xlink:href="/icons/icon-alarm.svg#icon-alarm" />
</svg>
Since /icons/icon-alarm.svg
is sitting there ready in cache, the browser will happily pluck it out of cache and display it.
(I was kind of amazed this works. Edge doesn’t like <use>
elements that link to files, but that’ll be over soon enough. Update, it’s over, Edge went Chromium.)
And even if the file isn’t in the cache, assuming we actually chuck this file on the file system likely the result of some kind of “include” (I used Nunjucks on the demo).
<use>
and inline SVG aren’t quite the same
But… True. What I like about the above is that it’s making use of the cache and the icons should render close to immediately. And there are some things you can style this way — for example, setting the fill on the parent icon should go through the shadow DOM that the <use>
creates and colorize the SVG elements within.
Still, it’s not the same. The shadow DOM is a big barrier compared to inline SVG.
So, enhance them! We could asynchronously load a script that finds each SVG icon, Ajaxs for the SVG it needs, and replaces the <use>
stuff…
const icons = document.querySelectorAll("svg.icon");
icons.forEach(icon => {
const url = icon.querySelector("use").getAttribute("xlink:href"); // Might wanna look for href also
fetch(url)
.then(response => response.text())
.then(data => {
// This is probably a bit layout-thrashy. Someone smarter than me could probably fix that up.
// Replace the <svg><use></svg> with inline SVG
const newEl = document.createElement("span");
newEl.innerHTML = data;
icon.parentNode.replaceChild(newEl, icon);
// Remove the <span>s
const parent = newEl.parentNode;
while (newEl.firstChild) parent.insertBefore(newEl.firstChild, newEl);
parent.removeChild(newEl);
});
});
Now, assuming this JavaScript executes correctly, this page has inline SVG available just like the original page did.
I didn’t understand how did it get to the cache in the first place?
You must have at least one svg icon with the icon inline?
Haven’t had the chance to try this yet, but the logic of it looks really interesting, at least until a proper solution is around. Thanks Chris.
Ok so what I got from this article is to use the SVG sprite technique but then use JS to swap out the content of the SVG with the real SVG data.
This is exactly what the svg4everybody polyfill does. It is intended to be used as a polyfill but (I think) you can turn the polyfill functionality off so that it applies to all browsers. (I might be misinterpreting the documentation).
I think I’ll post an issue in the repo with a feature request for this behaviour. I’m not sure it currently does what I think it does.
I’ve created a feature request. It doesn’t look like the project is maintained any more though so it’s unlikely to be implemented.
The easiest option is to probably store a copy of it locally and remove the if statements that check for external
<use>
browser support.Hmm, thanks, will give this a try. Built an icon management plugin called IconPress in which i’m only loading the icon sprite inline or AJAXd.
Would’ve preferred using external sprites, but due to browser inconsistencies like not being able to add CSS styles inside the symbol (nor sprite), gave up and went back embedding icons inline. The issue that i was having going inline, was caching them.
When compared to using awesome fonts that are better for site performance
I haven’t really tested the performance difference between icon fonts and inline SVG icons, but I do think that using icon fonts is more ideal.
While inline SVG icons is convenient for style individual elements within the icons, it does not separate presentation from content. In other words, those SVG codes exist in the HTML solely for styling purpose, and that is debatable to some.
In my opinion, using CSS pseudo-elements ::before and ::after to render icon fonts is a better approach, if styling individual elements within the icons is not necessary (it seldom is).
I’m assuming you’re referring to icon fonts such as Font Awesome? The issue with those is that, most of the time, the whole font is loaded instead of just the required icons for that page. This means you load hundreds of icons that will never be used. With an inline SVG, you only load what you need, nothing else. This is great for performance, and you can also use CSS to style individual parts of the SVG (stroke or fill!) whereas an icon font can only fill the shapes with a single color.
It’s not as much of a “drop-in” solution but it’s more performant and more customisable overall.
Could you use a document fragment instead of the span?
This would be a great use case for a custom element. Perhaps ?
I think this is the wrong way.
Inline SVG’s into CSS and it gets cached automatically and compressed serverside when enabled.
Please use
href
attribute in the example:Since
xlink:href
has been deprecated and it is also mentioned on one of the articles here from some years ago