Skip to main content
Back to Blog

How to Use SVG Icons in React and Next.js

A practical guide to the main approaches for using SVG icons in React and Next.js projects — from icon libraries to inline SVG and SVGR.

6 min read
react
nextjs
svg
tutorial

The Challenge

Using SVG icons in React and Next.js should be simple, but it often isn't. Between import configurations, bundle size concerns, and styling limitations, developers frequently spend more time setting up icons than building features.

This guide covers the main approaches, their trade-offs, and when to use each.

Approach 1: Icon Component Libraries

The most common starting point. Libraries like Lucide React, Heroicons, and React Icons provide pre-built SVG components.

Installation and usage (Lucide example):

npm install lucide-react
import { Search, Menu, Settings } from "lucide-react";

export function Toolbar() {
  return (
    <nav>
      <Search className="w-5 h-5" />
      <Menu className="w-5 h-5" />
      <Settings className="w-5 h-5" />
    </nav>
  );
}

Advantages:

  • Ready to use with no configuration
  • Tree-shakable — only imported icons are included in the bundle
  • Consistent style across the icon set
  • TypeScript support built in

Trade-offs:

  • Limited to icons available in the library
  • Each library has its own design style — mixing libraries can look inconsistent
  • Bundle size varies significantly between libraries

Bundle Size Matters

Not all icon libraries handle tree-shaking equally. When importing icons, pay attention to import paths. Some libraries require deep imports to avoid pulling in unused icons:

// Better: named imports from the main package (when tree-shaking works)
import { FaSearch } from "react-icons/fa";

// Avoid: importing from the root (can pull in all icons)
import { FaSearch } from "react-icons";

Lucide React and Heroicons are designed with ES Modules and provide predictable tree-shaking behavior out of the box.

Approach 2: Inline SVG Components

Writing SVG directly in JSX gives you complete control.

function SearchIcon({ className }: { className?: string }) {
  return (
    <svg
      xmlns="http://www.w3.org/2000/svg"
      viewBox="0 0 24 24"
      fill="none"
      stroke="currentColor"
      strokeWidth={2}
      className={className}
    >
      <circle cx="11" cy="11" r="8" />
      <path d="m21 21-4.3-4.3" />
    </svg>
  );
}

Advantages:

  • Zero additional dependencies
  • Full control over SVG attributes and styling
  • currentColor lets the icon inherit the parent's text color
  • No HTTP requests — the SVG is part of the component

Trade-offs:

  • Verbose for many icons
  • You need to source or create the SVG paths yourself
  • No tree-shaking benefit since each icon is a separate component anyway

Tip: This approach works well for custom icons that aren't available in standard libraries — such as AI-generated SVG icons tailored to your project.

Approach 3: SVGR (SVG Files as Components)

SVGR (opens in a new tab) converts .svg files into React components at build time.

Next.js webpack configuration:

// next.config.js
module.exports = {
  webpack(config) {
    config.module.rules.push({
      test: /\.svg$/i,
      issuer: /\.[jt]sx?$/,
      use: ["@svgr/webpack"],
    });
    return config;
  },
};

Then import SVGs as components:

import SearchIcon from "./icons/search.svg";

export function SearchBar() {
  return (
    <div>
      <SearchIcon className="w-5 h-5 text-gray-500" />
      <input placeholder="Search..." />
    </div>
  );
}

Advantages:

  • Use .svg files directly — no manual JSX conversion needed
  • SVGR runs SVGO optimization automatically
  • Good for teams with a designer who exports SVGs from Figma or Illustrator
  • TypeScript: add @svgr/webpack with TypeScript declarations for type safety

Trade-offs:

  • Requires webpack configuration (the issuer property is important to prevent conflicts with Next.js's built-in image handling)
  • May conflict with Turbopack in newer Next.js versions — check compatibility
  • Adds a build dependency

Approach 4: SVG Sprites

For sites with many icons, sprites reduce repetition by defining icons once and referencing them throughout the page.

// SpriteSheet.tsx — include once in your layout
export function SpriteSheet() {
  return (
    <svg style={{ display: "none" }}>
      <symbol id="icon-search" viewBox="0 0 24 24">
        <circle cx="11" cy="11" r="8" fill="none" stroke="currentColor" strokeWidth="2" />
        <path d="m21 21-4.3-4.3" fill="none" stroke="currentColor" strokeWidth="2" />
      </symbol>
      <symbol id="icon-menu" viewBox="0 0 24 24">
        <path d="M3 12h18M3 6h18M3 18h18" fill="none" stroke="currentColor" strokeWidth="2" />
      </symbol>
    </svg>
  );
}

// Usage anywhere
function Icon({ name, className }: { name: string; className?: string }) {
  return (
    <svg className={className}>
      <use href={`#icon-${name}`} />
    </svg>
  );
}

Advantages:

  • Icons defined once, reused everywhere
  • Good caching behavior
  • CSS Custom Properties can style icons inside <use> references (regular CSS values cannot cross this boundary)

Trade-offs:

  • More setup and maintenance overhead
  • Cross-browser performance varies — a stress test by Cloud Four found that Chrome/Edge handle external sprites significantly slower than Safari
  • Less intuitive for teams used to component-based workflows

Choosing the Right Approach

SituationRecommended Approach
Starting a new project, need standard icons fastIcon library (Lucide, Heroicons)
Custom icons from a designer (Figma/Illustrator)SVGR
A few unique, custom iconsInline SVG components
Large app with 100+ icons used repeatedlySVG sprites
Need icons not available in any libraryAI-generated SVG + inline component

Styling SVG Icons with Tailwind CSS

Since Tailwind CSS is widely used in React/Next.js projects, here are the common patterns:

// Size
<SearchIcon className="w-5 h-5" />   // 20px
<SearchIcon className="w-6 h-6" />   // 24px

// Color (using currentColor)
<SearchIcon className="text-gray-500" />
<SearchIcon className="text-blue-600 dark:text-blue-400" />

// Hover effects on parent
<button className="group">
  <SearchIcon className="w-5 h-5 text-gray-400 group-hover:text-blue-600 transition-colors" />
</button>

This works because most SVG icon libraries and inline SVGs use currentColor for their fill or stroke values.

Accessibility Checklist

Regardless of which approach you use, follow these accessibility practices:

Icons with meaning (e.g., icon-only buttons):

<button aria-label="Search">
  <SearchIcon aria-hidden="true" />
</button>

Decorative icons (next to visible text):

<button>
  <SearchIcon aria-hidden="true" />
  <span>Search</span>
</button>

The key rule: if the icon conveys meaning that isn't available through surrounding text, it needs an accessible label. If it's decorative, hide it from screen readers with aria-hidden="true".

Conclusion

For most React and Next.js projects, starting with an icon library (Lucide or Heroicons) covers standard UI needs. When you need custom icons, inline SVG components offer full control. SVGR bridges the gap for teams working with design tool exports.

When none of these options have the exact icon you need, consider generating custom SVGs with AI — describe what you want and get a production-ready SVG icon in moments.