SVG Optimization in React

·6 min read

SVG in React can go two ways: inline as a component (often via SVGR) or as an external file loaded via <img>. Each has different optimization implications. Here’s how to handle both.

Pattern 1: Inline SVG via SVGR

SVGR converts SVG files into React components. When you import an SVG in a Create React App or Vite project, the toolchain runs SVGR automatically:

import { ReactComponent as ArrowIcon } from './arrow.svg';
// or in Vite:
import ArrowIcon from './arrow.svg?react';

function Button() {
  return (
    <button>
      <ArrowIcon width={24} height={24} aria-hidden="true" />
      Submit
    </button>
  );
}

Inline SVG has two advantages: it’s directly styleable with CSS (you can change stroke/fill via currentColor), and there’s no extra HTTP request.

The downside: inline SVG adds to your JavaScript bundle. A 500-byte icon becomes 1 KB of JS overhead. For large icon sets this matters.

Optimize before SVGR processes it

SVGR does some basic cleaning, but running SVGO first produces better results. Add a pre-build script that optimizes all SVGs in your src/assets/ folder:

// scripts/optimize-icons.js
import { optimize } from 'svgo';
import fs from 'node:fs';
import path from 'node:path';

const dir = './src/assets/icons';
for (const file of fs.readdirSync(dir).filter(f => f.endsWith('.svg'))) {
  const full = path.join(dir, file);
  const result = optimize(fs.readFileSync(full, 'utf-8'), { multipass: true });
  fs.writeFileSync(full, result.data);
}
console.log('Icons optimized.');
// package.json
{
  "scripts": {
    "predev": "node scripts/optimize-icons.js",
    "prebuild": "node scripts/optimize-icons.js"
  }
}

Use currentColor for theming

Before optimizing, ensure your SVG uses currentColor for stroke and fill if you want to control colors via CSS:

<svg viewBox="0 0 24 24" fill="none">
  <path stroke="currentColor" stroke-width="2" d="M5 12h14"/>
</svg>

SVGO sometimes removes or replaces currentColor. If that happens, configure the convertColors plugin to skip it.

Pattern 2: External SVG files via <img>

For decorative images and illustrations that don’t need CSS theming, use an<img> tag. The browser caches external SVGs independently of your JS bundle.

function Logo() {
  return (
    <img
      src="/images/logo.svg"
      alt="Acme Inc."
      width={120}
      height={40}
    />
  );
}

Make sure the file in public/ is optimized before deployment. A CI step or pre-commit hook is the safest way to ensure this (see CI/CD automation).

Pattern 3: SVG sprite for icon sets

If you have 50+ icons, a sprite reduces HTTP requests significantly. One SVG file contains all icons as <symbol> elements; each icon is rendered with<use>:

// Load once at the top of your app
import sprite from './icons/sprite.svg';

function Icon({ name, size = 24 }) {
  return (
    <svg width={size} height={size} aria-hidden="true">
      <use href={`${sprite}#${name}`} />
    </svg>
  );
}

// Usage
<Icon name="arrow" size={24} />

See SVG sprite optimization for how to build and optimize a sprite.

Bundle size: inline vs. external

ApproachBundle impactHTTP requestCSS theming
Inline (SVGR)+size per iconNoneYes
External <img>None1 per iconNo
SVG spriteNone1 totalLimited

Performance tip: preload critical SVGs

If an SVG is visible above the fold and affects LCP, add a preload hint in your<head>:

<link rel="preload" href="/hero.svg" as="image" type="image/svg+xml" />

For more on SVG and performance, see SVG file size and web performance.

For Next.js specifically

Next.js has its own SVG handling quirks. See the dedicated guide on SVG optimization in Next.js.

Optimize SVGs instantly — no setup needed

500 free optimizations per month. No credit card required.

Get a free API key →