How to build a performant icon library with React

Alexis Charp
4 min readDec 16, 2024

--

Managing icons efficiently in a React-based environment can be challenging. From optimising assets to ensuring performance and developer experience, there are many considerations. This guide walks you through a step-by-step approach to building a performant icon library, leveraging best practices and modern tools.

Optimise your SVG assets

Icons typically start as SVG files exported from design tools like Figma. These raw SVGs often contain unnecessary metadata, comments, or styles, making them heavier than necessary. Use tools like SVGO to optimise these files, reducing their size significantly while preserving visual quality.

Example workflow

  • Export icons from Figma.
  • Run them through SVGO or a web-based optimiser like SVGOMG.
  • Store optimised files in a dedicated assets/icons directory.

Avoid excessive Javascript with sprites

A common mistake is transforming every SVG into a React component using tools like SVGR. While this approach simplifies integration, it can significantly increase your Javascript bundle size. This happens because React components, even when unused, may inadvertently bypass tree-shaking optimisations.

Instead, consider grouping all SVGs into a single sprite file. This method keeps your Javascript footprint minimal while still providing a flexible and performant way to use icons.

How SVG sprite work

  • Combine all SVGs into a single file using a tool like sprite.sh.
  • Each icon is wrapped in a <symbol> tag with a unique id.
  • Reference icons using the <use> element and the corresponding id.
<!-- Example Sprite -->
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
<symbol id="arrow-down" viewBox="0 0 24 24">
<path d="..." />
</symbol>
</svg>

<!-- Using an Icon -->
<svg>
<use href="#arrow-down" />
</svg>

Build a reusable Icon component

To simplify usage and ensure consistency, create a generic <Icon /> component in React. This component abstracts the implementation details and makes it easy to include icons dynamically by passing an icon prop.

Implementation example

export const Icon = ({ icon, ...props }: { icon: string }) => (
<svg {...props}>
<use href={`/sprite.svg#${icon}`} />
</svg>
);

// The icon prop could also be defined as a union type
// This ensures only valid icons are used

Serve your SVG sprite

When using SVG sprites, you have two main options for serving them: injecting the sprite directly into the DOM or serving it as an external file. While injecting the sprite directly ensures it’s immediately available without an additional HTTP request, it prevents the browser from caching the sprite effectively. This can lead to redundant data being sent on every page load, especially in applications with multiple pages.

For most use cases, I recommend serving the sprite as an external file. This approach allows the browser to cache the sprite, minimising the impact on network performance and ensuring faster subsequent loads. However, there’s a critical caveat: the sprite file must be served from the same domain as your application. This is because the <use> element that references symbols within the sprite does not support cross-origin requests.

Implementation example

Here’s how you can ensure the sprite file is served from your application’s domain. In frameworks like Next.js, you can automate the process during the build step by copying the sprite to the public/ directory.

const CopyPlugin = require('copy-webpack-plugin');
const path = require('path');

module.exports = {
webpack: (config) => {
config.plugins.push(
new CopyPlugin({
patterns: [
{
from: './path/to/sprite.svg',
to: path.resolve(__dirname, 'public/sprite.svg'),
},
],
}),
);
return config;
},
};

This setup ensures the sprite file is accessible at /sprite.svg, allowing your application to reference it easily without any cross-origin restrictions. By serving the sprite from a static path within your application, you maintain control over its availability and keep the implementation seamless for your development team.

Benefits of the sprite approach

Switching to a sprite-based strategy offers several advantages.

Improved Performance

  • Icons are no longer included in the Javascript bundle.
  • A single sprite file is cached by the browser, reducing network requests for subsequent page loads.

Simplified maintenance

  • One central file for all icons.
  • No need to maintain individual React components for each icon.

Developer Experience

  • The <Icon />component ensures a consistent API.
  • When the component is strongly typed, developers can quickly access icons with auto-completion in their IDE.

Conclusion

By combining SVG optimisation, sprite-based icon management, and a reusable React <Icon /> component, you can build a performant, scalable, and developer-friendly icon library. This approach minimises Javascript overhead, leverages browser caching, and simplifies integration, making it a win-win for both developers and users.

Thank you for taking the time to read this article! I’m Alexis Charpentier, a French front-end developer passionate about crafting beautiful and accessible experiences. I build design systems and collaborate closely with UX/UI designers to bring ideas to life. As a teacher, I love sharing my knowledge and helping others grow in their own journeys.

If you enjoyed this article, I’d truly appreciate a follow here on Medium as it’s the best way to support my work and stay updated on my future writings. I’m always happy to discuss, answer questions, or hear your thoughts in the comments.

Your support and engagement mean a lot — merci beaucoup!

--

--

Alexis Charp
Alexis Charp

Written by Alexis Charp

Passionate front-end developer. Inspired to develop beautiful interfaces. Fueled by music and football.

No responses yet