Developers often insert SVG directly into JSX. This is convenient to use, but it increases the JS bundle size. In the pursuit of optimization, I decided to find another way of using SVG icons without cluttering the bundle. We will talk about SVG sprites, what they are, how to use them, and what tools are available for working with them. Starting with theory, we will write a script that generates an SVG sprite step by step and conclude by discussing plugins for and . vite webpack What Is SVG Sprite? An image sprite is a collection of images placed into a single image. In its turn, SVG Sprite is a collection of SVG content, wrapped into , which is placed into . <symbol /> <svg /> For example, we have a simple SVG pen icon: To obtain an SVG sprite, we will replace the tag with , and wrap it with externally: <svg /> <symbol /> <svg /> Now it is an SVG sprite, and we have an icon inside with . id="icon-pen" Ok, but we should figure out how to place this icon on our HTML page. We will use the tag with the attribute, specifying the ID of the icon, and it will duplicate this element inside SVG. <use /> href Let's take a look at an example of how works: <use /> In this example, there are two circles. The first one has a blue outline, and the second is a duplicate of the first one but with a red fill. Let’s get back to our SVG sprite. Along with the usage of , we will get this: <use /> Here, we have a button with our pen icon. So far, we have used an icon without its code in . If we have more than one button on the page, it will not affect more than once the size of our HTML layout because all icons will come from our SVG sprite and will be reusable. <button /> Creating SVG Sprite File Let's move our SVG sprite into a separate file so that we don't have to clutter the file. First, create a file and put a SVG sprite into it. The next step is to provide access to the icon using the attribute in : index.html sprite.svg href <use/> Automating SVG Sprite Creation To save a lot of time on icon usage, let’s set up an automation for this process. To get easy access to icons and manage them as we want, they have to be separated, each in its own file. First, we should put all icons in the same folder, for example: Now, let’s write a script that grabs these files and combines them into a single SVG sprite. Create the file in the root directory of your project. generateSvgSprite.ts Install library: glob npm i -D glob Get an array of full paths for each icon using : globSync Now, we will iterate each file path and get file content using Node's built-in library : fs Great, we have the SVG code of each icon, and now we can combine them, but we should replace the tag inside each icon with the tag and remove useless SVG attributes. svg symbol We should parse our SVG code with some HTML parser library to get its DOM representation. I will use : node-html-parser We have parsed the SVG code and obtained the SVG element as if it were a real HTML element. Using the same parser, create an empty element to move children of to : symbol svgElement symbol After extracting children from , we should also get the and attributes from it. As an , let’s set the name of the icon file. svgElement id viewBox id Now, we have a element that can be placed in an SVG sprite. So, just define the variable before iterating the files, transform the into a string, and push it into : symbol symbols symbolElement symbols The final step is to create the SVG sprite itself. It represents a string with in “root” and symbols as children: svg const svgSprite = `<svg>${symbols.join('')}</svg>`; And if you are not considering using plugins, which I will talk about below, you need to put the file with the created sprite in some static folder. Most bundlers use a folder: public fs.writeFileSync('public/sprite.svg', svgSprite); And this is it; the script is ready to use: // generateSvgSprite.ts import { globSync } from 'glob'; import fs from 'fs'; import { HTMLElement, parse } from 'node-html-parser'; import path from 'path'; const svgFiles = globSync('src/icons/*.svg'); const symbols: string[] = []; svgFiles.forEach(file => { const code = fs.readFileSync(file, 'utf-8'); const svgElement = parse(code).querySelector('svg') as HTMLElement; const symbolElement = parse('<symbol/>').querySelector('symbol') as HTMLElement; const fileName = path.basename(file, '.svg'); svgElement.childNodes.forEach(child => symbolElement.appendChild(child)); symbolElement.setAttribute('id', fileName); if (svgElement.attributes.viewBox) { symbolElement.setAttribute('viewBox', svgElement.attributes.viewBox); } symbols.push(symbolElement.toString()); }); const svgSprite = `<svg>${symbols.join('')}</svg>`; fs.writeFileSync('public/sprite.svg', svgSprite); You can put this script in the root of your project and run it with : tsx npx tsx generateSvgSprite.ts Actually, I’m using here because I used to write code in TypeScript everywhere, and this library allows you to execute node scripts written in TypeScript. If you want to use pure JavaScript, then you can run it with: tsx node generateSvgSprite.js So, let’s sum up what the script is doing: It looks into folder for any files. src/icons .svg It Extracts the content of every icon and creates a symbol element from it. It Wraps up all the symbols into a single <svg />. It creates file in the folder. sprite.svg public How to Change Icon Colors Let's cover one frequent and important case: colors! We created a script where the icon goes into a sprite, but this icon can have different colors throughout the project. We should keep in mind that not only elements can have fill or stroke attributes, but also , , , and others. There’s a very useful CSS feature that will help us - . <svg/> path circle line currentcolor This keyword represents the value of an element's color property. For example, if we use the on an element that has a , then this element will have a red background. color: red background: currentcolor Basically, we need to change every stroke or fill attribute value to the . I hope you are not seeing it done manually, heh. And even writing some code that will replace or parse SVG strings is not very efficient compared to a very useful tool . currentcolor svgo This is an SVG optimizer that can help not only with colors but also with removing redundant information from SVG. Let’s install : svgo npm i -D svgo has built-in plugins, and one of them is , which has the property . If we use this SVG output, it will replace colors with . Here is the usage of along with : svgo convertColors currentColor: true currentcolor svgo convertColors import { optimize } from 'svgo'; const output = optimize( '<svg viewBox="0 0 24 24"><path fill="#000" d="m15 5 4 4" /></svg>', { plugins: [ { name: 'convertColors', params: { currentColor: true, }, } ], } ) console.log(output); And the output will be: <svg viewBox="0 0 24 24"><path fill="currentColor" d="m15 5 4 4"/></svg> Let’s add into our magical script which we wrote in the previous part: svgo // generateSvgSprite.ts import { globSync } from 'glob'; import fs from 'fs'; import { HTMLElement, parse } from 'node-html-parser'; import path from 'path'; import { Config as SVGOConfig, optimize } from 'svgo'; // import `optimize` function const svgoConfig: SVGOConfig = { plugins: [ { name: 'convertColors', params: { currentColor: true, }, } ], }; const svgFiles = globSync('src/icons/*.svg'); const symbols: string[] = []; svgFiles.forEach(file => { const code = fs.readFileSync(file, 'utf-8'); const result = optimize(code, svgoConfig).data; // here goes `svgo` magic with optimization const svgElement = parse(result).querySelector('svg') as HTMLElement; const symbolElement = parse('<symbol/>').querySelector('symbol') as HTMLElement; const fileName = path.basename(file, '.svg'); svgElement.childNodes.forEach(child => symbolElement.appendChild(child)); symbolElement.setAttribute('id', fileName); if (svgElement.attributes.viewBox) { symbolElement.setAttribute('viewBox', svgElement.attributes.viewBox); } symbols.push(symbolElement.toString()); }); const svgSprite = `<svg xmlns="http://www.w3.org/2000/svg">${symbols.join('')}</svg>`; fs.writeFileSync('public/sprite.svg', svgSprite); And run the script: npx tsx generateSvgSprite.ts As a result, SVG sprite will contain icons with . And these icons can be used everywhere in the project with any color you want. currentColor Plugins We have a script, and we can run it whenever we want, but it is slightly inconvenient that we should use it manually. So, I recommend a few plugins that can watch our files and generate SVG sprites on the go: .svg (for users) vite-plugin-svg-spritemap vite This is my plugin which contains basically this script that we just created in this article. The plugin has replacement enabled by default, so you can set up the plugin pretty easily. currentColor // vite.config.ts import svgSpritemap from 'vite-plugin-svg-spritemap'; export default defineConfig({ plugins: [ svgSpritemap({ pattern: 'src/icons/*.svg', filename: 'sprite.svg', }), ], }); (for users) svg-spritemap-webpack-plugin webpack I used this Webpack plugin until I switched to Vite. But this plugin is still a good solution if you are using Webpack. You should manually enable color conversion, and it will look like this: // webpack.config.js const SVGSpritemapPlugin = require('svg-spritemap-webpack-plugin'); module.exports = { plugins: [ new SVGSpritemapPlugin('src/icons/*.svg', { output: { svgo: { plugins: [ { name: 'convertColors', params: { currentColor: true, }, }, ], }, filename: 'sprite.svg', }, }), ], } Usage in Layout I will provide an example in , but you can implement it where you want because it is mostly about HTML. So, as we have in our build folder, we can access the sprite file and create the basic component: React sprite.svg Icon const Icon: FC<{ name: string }> = ({ name }) => ( <svg> <use href={`/sprite.svg#${name}`} /> </svg> ); const App = () => { return <Icon name="pen" />; }; The Final Result So, summarizing everything, to prevent a lot of manual work with icons, we: can easily save and keep organized icons in the project with desirable names have a script that combines all icons into a single sprite in a separate file that reduces bundle size and allows us to use these icons anywhere in the project have a useful tool that helps us keep icons decluttered from unnecessary attributes and change colors in the place of usage have a plugin that can watch our icon files and generate sprites on the go as a part of the build process have an Icon component that is a cherry on top Conclusion Efficiency in development isn't just about saving time; it's about unlocking our creative potential. Automating the nitty-gritty tasks like managing icons isn't just a shortcut; it's a gateway to a smoother, more impactful coding experience. And saving time on such routine stuff, you can focus on more complex tasks and grow as a developer faster.