Most of the popular icon libraries for React load the icons in an inefficient way, directly to JSX. There is a great twitter thread about the performance of this here, as well as a great article explaining the different methods and their trade-offs.
Today I'm going to show you how we can utilize svg sprite sheets in NextJs.
Credit
The code I will be using is slightly modified from Kent C. Dodds Epic Stack
implementation.
His is for Remix.js. I have modified a few parts to make it work on NextJs.
Getting Started
1. Create a folder to hold our icons
In the root level of your NextJs project, create a folder called /other/svg.
This is where we store our SVGs files. If you are using different icon libraries, its also good to create subfolders for those, such as /other/svg/lucide.
Now to add a icon, you can just copy its svg into a .svg file, but I'm going to show you a great tool to automate this for the popular React icon libraries.
Sly-cli is a tool that adds code from dependencies, but not the dependencies themselves. This will let us get the icon libraries svg file without all the React layers.
For example, to add the avatar and bell icon for radix icons, we would do this:
You can also use:
To use interactive mode to set this up easily from the terminal, as well as see the icons you can use listed, to easily select the ones you want. I will show you my sly.json later for a good starter configuration
2. Compiling the icons into a sprite
An SVG sprite utilizes the <symbol> for each of our icons, to become an element in one SVG. To generate this, we are going to utilise a script. You will have to run this each time you add a icon. If you used sly, you can set this up to run automatically after you add them. You can see this in my example sly.json I will leave in this blog.
First we need to install some dependencies:
Then we need the script. For NextJs, you want to create a file at the root level called build-icons.mts(Note: the .mts file extension is needed for the default NextJs app, unless you are running your app in module mode)
Next, we want to add the command to run this script to out package.json.
Now if we run npm run build:icons, you should see a sprite.svg file appear in /public/icons. It will also create a name.d.ts file in types. This is for TypeScript and helps us ensure we are referencing the correct icon.
3. Using SVG Sprites in NextJs
We can begin to use our icons within an SVG like below, where name is the icon name. If you used subfolder in the icon folder, the name will be folder/icon such as radix/camera.
To create a reusable, typesafe and extensible Icon component, we can do something like this:
This component uses the cn tailwind helper function. If you do not already have it you can create that in lib/cn.ts
Now to use our Icon component, we simply do:
Where name is the icon name. If you want the icon to appear to the left of text and be aligned, the component above can do that for you if you pass the text in as a child:
Note that you can pass in classNames to the child element using childClassName, or just use your own nested element.
4. Preload the sprite
To repload the sprite so its loaded and ready to use on page load, we need to create a client component for the NextJs App Router, called preload-resources.tsx
Then we want to use this at our top most layout.tsx
5. Sly configuration
If you have used sly to help with creating the SVG files, here is my example configuration for Lucide and Radix Icons.
This will run the build script for you after adding icons.
Wrapping Up
Hopefully the above helps you get started with using SVG sprites in NextJs and React. Its a great approach with good performance compared to alternatives. You can view the example repo, as well as the components and scripts here
One drawback to sprites is you don't typically install a library of icons and then use them like regular components, we have to use the build script. Further, there is no "tree-shaking" for sprites, all the icons you add will be added to it. So make sure you only add the ones you use.