📝 SVG Icon Optimization
We will have a look at SVG Icon optimization, in this Deno Fresh SVG sprites post. Without care, SVG icons can take up half of the bytes of HTML code shipped for a particular page. This is especially true working in React and embedding the SVG inline, in React components.
In this post, we will see how you can create an SVG sprite sheet for all icons on a page, or a site. Loading that sheet as an independent, resource from an HTML use
tag is a more performant alternative than adding the SVG content inline. We shall see, this approach also allows you easily to style the icons.
We see further optimizations, making the sprite sheet a static asset with Deno Fresh’s cache-busting feature. Finally, you get some example code for using the SVGO plugin in Deno to minify the sprite sheet. Hopefully, this is what you were looking for, so let’s get going. The code and approach is based on an excellent blog post by Ben Adam, which goes into the details of how it works together with limitations. We won’t revisit those aspects here.
🧱 What are we Building?
I put together a basic demo for the post, though you should be able just to copy and paste code snippets into a new or existing project, which you are already working on. There is a link to the full demo code further down.
Using these techniques, I reduced the size of shipped HTML by more than 25% (after compression) for a site I was working on.
🔎 Finding Icons
If you are not new here, you probably already know that Icônes by Anthony Fu is my “go to” source of SVG icons. It is a huge library with dozens of icon collections, and you are unlikely to be unable to find any icon you are looking for there.
Although Icônes provides downloadable React components for each icon, here we need the raw SVG. To get going, go to the Icônes all-collection search page to find and select an icon.
When you select your chosen icon, a pop-up will appear at the bottom of the window with the various format options. From Snippets, select SVG. This will copy the SVG code to your clipboard.
Example SVG Icon Code
Here is the SVG code for the Simple Icons Deno icon (used in the example image above):
<svg
xmlns="http://www.w3.org/2000/svg"
width="32"
height="32"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M12 0c6.627 0 12 5.373 12 12s-5.373 12-12 12S0 18.627 0 12S5.373 0 12 0Zm-.469 6.793c-3.49 0-6.204 2.196-6.204 4.928c0 2.58 2.498 4.228 6.37 4.145l.118-.003l.425-.012l-.109.279l.013.029c.031.072.06.145.084.22l.01.028l.015.045l.021.065l.014.045l.014.047l.015.049l.021.075l.022.079l.015.054l.023.084l.022.088l.023.091l.023.095l.015.065l.024.1l.023.103l.032.143l.017.074l.024.114l.024.117l.025.12l.035.174l.029.142l.037.195l.02.1l.028.155l.03.158l.039.217l.04.225l.04.231l.041.24l.042.246l.042.254l.042.26l.032.201l.055.344l.022.14l.055.36l.045.295l.034.227l.046.308l.023.156a10.758 10.758 0 0 0 6.529-3.412l.05-.055l-.238-.891l-.633-2.37l-.395-1.47l-.348-1.296l-.213-.787l-.136-.498l-.081-.297l-.073-.264l-.032-.11l-.018-.064l-.01-.034l-.008-.026a6.042 6.042 0 0 0-2.038-2.97c-1.134-.887-2.573-1.351-4.252-1.351ZM8.467 19.3a.586.586 0 0 0-.714.4l-.004.013l-.527 1.953c.328.163.665.309 1.008.437l.08.03l.57-2.114l.004-.015a.586.586 0 0 0-.417-.704Zm3.264-1.43a.586.586 0 0 0-.715.4l-.004.014l-.796 2.953l-.004.014a.586.586 0 0 0 1.131.305l.004-.014l.797-2.953l.003-.014a.585.585 0 0 0 .013-.067l.002-.022l-.019-.096l-.027-.138l-.018-.086a.584.584 0 0 0-.367-.295Zm-5.553-3.04a.59.59 0 0 0-.037.09l-.005.02l-.797 2.953l-.004.014a.586.586 0 0 0 1.131.306l.004-.014l.723-2.678a5.295 5.295 0 0 1-1.015-.692Zm-1.9-3.397a.586.586 0 0 0-.715.4l-.004.013l-.797 2.953l-.003.015a.586.586 0 0 0 1.13.305l.005-.014l.797-2.953l.003-.015a.586.586 0 0 0-.416-.704Zm17.868-.67a.586.586 0 0 0-.715.399l-.004.014l-.797 2.953l-.003.014a.586.586 0 0 0 1.13.305l.005-.014l.797-2.953l.003-.014a.586.586 0 0 0-.416-.704ZM2.542 6.82a10.707 10.707 0 0 0-1.251 3.926a.586.586 0 0 0 1.002-.22l.004-.014l.797-2.953l.003-.014a.586.586 0 0 0-.555-.725Zm17.585.02a.586.586 0 0 0-.714.4l-.004.014l-.797 2.953l-.004.014a.586.586 0 0 0 1.131.305l.004-.014l.797-2.953l.004-.014a.586.586 0 0 0-.417-.704Zm-7.846 1.926a.75.75 0 1 1 0 1.5a.75.75 0 0 1 0-1.5Zm-6.27-4.733a.586.586 0 0 0-.715.398l-.004.015l-.797 2.953l-.004.014a.586.586 0 0 0 1.132.305l.003-.014l.797-2.953l.004-.014a.586.586 0 0 0-.417-.704Zm10.238.558a.586.586 0 0 0-.714.399l-.004.014l-.536 1.984c.347.171.678.373.99.603l.051.038l.626-2.32l.004-.014a.586.586 0 0 0-.417-.704Zm-5.211-3.33a10.76 10.76 0 0 0-1.115.158l-.078.015l-.742 2.753l-.004.015a.586.586 0 0 0 1.131.305l.004-.014l.797-2.953l.004-.015a.583.583 0 0 0 .003-.264Zm7.332 2.04l-.156.58l-.004.015a.586.586 0 0 0 1.131.305l.004-.014l.017-.063a10.838 10.838 0 0 0-.923-.772l-.069-.051Zm-4.636-1.944l-.283 1.048l-.003.014a.586.586 0 0 0 1.13.305l.005-.014l.297-1.102c-.35-.097-.705-.176-1.063-.237l-.083-.014Z"
/>
</svg>
You can see the full SVG for just one, simple icon is already a fair few bytes! In the next section, we start building the component which pulls in the path from a sprite sheet, rather than inlining it.
🧩 Creating an SVG Component with use
Element
Here is the code for a Preact component, which will produce the SVG icon above. The file path in the demo code is components/Icons/Deno.tsx
:
import { asset } from "$fresh/runtime.ts";
import { FunctionComponent } from "preact";
interface DenoIconProps {
colour?: string;
height?: number;
width: number;
}
export const DenoIcon: FunctionComponent<DenoIconProps> = function DenoIcon({
colour = "inherit",
width,
height = width,
}) {
// SVG path by Simple Icons (https://simpleicons.org/) via Icônes (https://icones.js.org/collection/all)
return (
<svg
width={`${width}px`}
height={`${height}px`}
style={{ color: colour }}
>
<use href={`${asset("/sprite.svg")}#deno`} />
</svg>
);
};
Following Ben’s method, the Preact component is an HTML use
tag sandwiched inside an SVG tag. We can include styles on the SVG tag, and the path will go into the sprite sheet. The final sprite sheet will be a static asset in the static
directory of the project. deno
id
in the href
path is our own identifier, used to pick the right icon from the sprite sheet.
The Deno Fresh cache-busting comes from wrapping our path in the asset
function (imported from $fresh/runtime
in line 1
). Deno will add a hash to the end of the path in the output site. This lets us set a long cache time, since the path will change whenever the sprite sheet changes, and the new sheet will be fetched, rather than recycling any cached version.
🥤 Adding the SVG Icon to the Sprite Sheet
The base sprite template will look like this (file path is assets/sprite.svg
in the demo code):
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
>
<defs>
</defs>
</svg>
We will add each icon as a symbol
element, with id
inside the defs
element. In the next section, we optimize this file and output the optimized version to static/sprite.svg
. Here is the completed version with a couple of icons:
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
>
<defs>
<symbol viewBox="0 0 24 24" id="deno">
<path
fill="currentColor"
d="M12 0c6.627 0 12 5.373 12 12s-5.373 12-12 12S0 18.627 0 12S5.373 0 12 0Zm-.469 6.793c-3.49 0-6.204 2.196-6.204 4.928c0 2.58 2.498 4.228 6.37 4.145l.118-.003l.425-.012l-.109.279l.013.029c.031.072.06.145.084.22l.01.028l.015.045l.021.065l.014.045l.014.047l.015.049l.021.075l.022.079l.015.054l.023.084l.022.088l.023.091l.023.095l.015.065l.024.1l.023.103l.032.143l.017.074l.024.114l.024.117l.025.12l.035.174l.029.142l.037.195l.02.1l.028.155l.03.158l.039.217l.04.225l.04.231l.041.24l.042.246l.042.254l.042.26l.032.201l.055.344l.022.14l.055.36l.045.295l.034.227l.046.308l.023.156a10.758 10.758 0 0 0 6.529-3.412l.05-.055l-.238-.891l-.633-2.37l-.395-1.47l-.348-1.296l-.213-.787l-.136-.498l-.081-.297l-.073-.264l-.032-.11l-.018-.064l-.01-.034l-.008-.026a6.042 6.042 0 0 0-2.038-2.97c-1.134-.887-2.573-1.351-4.252-1.351ZM8.467 19.3a.586.586 0 0 0-.714.4l-.004.013l-.527 1.953c.328.163.665.309 1.008.437l.08.03l.57-2.114l.004-.015a.586.586 0 0 0-.417-.704Zm3.264-1.43a.586.586 0 0 0-.715.4l-.004.014l-.796 2.953l-.004.014a.586.586 0 0 0 1.131.305l.004-.014l.797-2.953l.003-.014a.585.585 0 0 0 .013-.067l.002-.022l-.019-.096l-.027-.138l-.018-.086a.584.584 0 0 0-.367-.295Zm-5.553-3.04a.59.59 0 0 0-.037.09l-.005.02l-.797 2.953l-.004.014a.586.586 0 0 0 1.131.306l.004-.014l.723-2.678a5.295 5.295 0 0 1-1.015-.692Zm-1.9-3.397a.586.586 0 0 0-.715.4l-.004.013l-.797 2.953l-.003.015a.586.586 0 0 0 1.13.305l.005-.014l.797-2.953l.003-.015a.586.586 0 0 0-.416-.704Zm17.868-.67a.586.586 0 0 0-.715.399l-.004.014l-.797 2.953l-.003.014a.586.586 0 0 0 1.13.305l.005-.014l.797-2.953l.003-.014a.586.586 0 0 0-.416-.704ZM2.542 6.82a10.707 10.707 0 0 0-1.251 3.926a.586.586 0 0 0 1.002-.22l.004-.014l.797-2.953l.003-.014a.586.586 0 0 0-.555-.725Zm17.585.02a.586.586 0 0 0-.714.4l-.004.014l-.797 2.953l-.004.014a.586.586 0 0 0 1.131.305l.004-.014l.797-2.953l.004-.014a.586.586 0 0 0-.417-.704Zm-7.846 1.926a.75.75 0 1 1 0 1.5a.75.75 0 0 1 0-1.5Zm-6.27-4.733a.586.586 0 0 0-.715.398l-.004.015l-.797 2.953l-.004.014a.586.586 0 0 0 1.132.305l.003-.014l.797-2.953l.004-.014a.586.586 0 0 0-.417-.704Zm10.238.558a.586.586 0 0 0-.714.399l-.004.014l-.536 1.984c.347.171.678.373.99.603l.051.038l.626-2.32l.004-.014a.586.586 0 0 0-.417-.704Zm-5.211-3.33a10.76 10.76 0 0 0-1.115.158l-.078.015l-.742 2.753l-.004.015a.586.586 0 0 0 1.131.305l.004-.014l.797-2.953l.004-.015a.583.583 0 0 0 .003-.264Zm7.332 2.04l-.156.58l-.004.015a.586.586 0 0 0 1.131.305l.004-.014l.017-.063a10.838 10.838 0 0 0-.923-.772l-.069-.051Zm-4.636-1.944l-.283 1.048l-.003.014a.586.586 0 0 0 1.13.305l.005-.014l.297-1.102c-.35-.097-.705-.176-1.063-.237l-.083-.014Z"
/>
</symbol>
<symbol viewBox="0 0 512 512" id="lemon">
<!-- TRUNCATED -->
</symbol>
</defs>
</svg>
Notice:
- in lines
6
and14
, we use theviewBox
values from the source SVG; - we place the
path
elements inside thesymbol
tag; and - we must remember to add an
id
matching theid
given in the corresponding component.
If your SVG has a defs
tag, just include the icon’s defs
tag contents above the symbol
element for your icon.
🔥 Optimizing the Sprite
As a final step, you can add a Deno script to optimize the static, served sprite. First, update deno.json
:
{
"lock": false,
"tasks": {
"start": "deno run -A --watch=static/,routes/ dev.ts",
"update": "deno run -A -r https://fresh.deno.dev/update .",
"minify": "deno run --allow-env=DENO_ENV --allow-read --allow-run --allow-write minify-svg-sprite.ts"
},
"imports": {
"@/": "./",
"$fresh/": "https://deno.land/x/fresh@1.2.0/",
"preact": "https://esm.sh/preact@10.15.1",
"preact/": "https://esm.sh/preact@10.15.1/",
"preact-render-to-string": "https://esm.sh/*preact-render-to-string@6.1.0",
"@preact/signals": "https://esm.sh/*@preact/signals@1.1.3",
"@preact/signals-core": "https://esm.sh/*@preact/signals-core@1.2.3",
"$std/": "https://deno.land/std@0.193.0/",
"svgo": "https://esm.sh/svgo@3.0.2/"
},
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "preact"
}
}
Then create the script minify-svg-sprite.ts
in the project root directory:
import { optimize } from "svgo";
const svgString = await Deno.readTextFile("./assets/sprite.svg");
const { data: optimisedSVG } = optimize(svgString, {
path: "./assets/sprite.svg",
});
await Deno.writeTextFile("./static/sprite.svg", optimisedSVG);
Deno.exit(0);
You can now use the SVG icon components on your site pages:
import { DenoIcon } from "@/components/Icons/Deno.tsx";
import { LemonIcon } from "@/components/Icons/Lemon.tsx";
export default function Home() {
return (
<main>
<DenoIcon colour="#fff" width={192} />
<LemonIcon width={192} />
</main>
);
}
💯 Deno Fresh SVG Sprites: Checking Your Work
Run the minify script, and check it outputs a minified version of the sprite to static/sprite.svg
. Remember to re-run this script whenever you add new icons to the sprite.
If you pull open dev tools and have a look at the Network tab, you should see the sprite is downloaded. The path should have a hash on the end of it (something like sprite.svg?__frsh_c=8d89...
).
Jumping onto the Inspector tab, use should find a use
element, instead of the inline SVG code for the icon (though you can expand the enclosed #shadow-root
to inspect the SVG included from the sprite).
🙌🏽 Deno Fresh SVG Sprites: Wrapping Up
We saw how you can set up Deno Fresh SVG Sprites in your own project. In particular, we saw:
- how you can use Icônes to find SVG icons;
- how you make sure create an SVG sprite sheet for use with Preact components; and
- a way to optimize SVG assets in Deno.
The complete code for this post. I do hope the post has either helped you with an existing project or provided some inspiration for getting started with setting up a new one.
Get in touch if you have some questions about this content or suggestions for improvements. You can add a comment below if you prefer. Also, reach out with ideas for fresh content on Deno or other topics!
🙏🏽 Deno Fresh SVG Sprites: Feedback
Have you found the post useful? Would you prefer to see posts on another topic instead? Get in touch with ideas for new posts. Also, if you like my writing style, get in touch if I can write some posts for your company site on a consultancy basis. Read on to find ways to get in touch, further below. If you want to support posts similar to this one and can spare a few dollars, euros or pounds, then please consider supporting me through Buy me a Coffee.
Finally, feel free to share the post on your social media accounts for all your followers who will find it useful. As well as leaving a comment below, you can get in touch via @askRodney on Twitter, @rodney@toot.community on Mastodon and also the #rodney Element Matrix room. Also, see further ways to get in touch with Rodney Lab. I post regularly on Astro as well as Deno. Also, subscribe to the newsletter to keep up-to-date with our latest projects.