Import SVG as React Components with Vite SVGR

By the Axialis Engineering team ·

React SVG Icons with vite-svgr

Shipping raw <img> icons in React costs you CSS theming, accessibility props, and tree-shaking, and dropping inline SVG into JSX bloats your components. The fix is SVGR: import a .svg file and get back a real React component you can style with currentColor, pass className and title to, and bundle only where it is used.

This guide wires up vite-plugin-svgr for a Vite project and @svgr/webpack for Next.js, adds the TypeScript module declarations both need, and starts from a minified IconVectors SVG that already paints with currentColor and keeps a clean viewBox.

Export a clean, themable SVG from IconVectors

  1. Open or create your icon:
    • File → Open… (Ctrl+O) or New Icon (Ctrl+N).
    • Set fills/strokes to currentColor so the icon inherits CSS/text color in React.
    A 24x24 camera icon open in the IconVectors editor, with the grid, layer list, and pixel/vector preview
    Export a minified SVG via File → Export → Export Minified (Shift+Ctrl+M) to strip metadata and keep a clean viewBox.

Vite (React + TypeScript): install & configure SVGR

  1. Install
    npm i -D vite-plugin-svgr
    # or: pnpm add -D vite-plugin-svgr
    # or:  yarn add -D vite-plugin-svgr
  2. Configure vite.config.ts
    import { defineConfig } from 'vite'
    import react from '@vitejs/plugin-react'
    import svgr from 'vite-plugin-svgr'
    
    export default defineConfig({
      plugins: [
        react(),
        svgr({
          svgrOptions: {
            // Keep viewBox for proper scaling; strip width/height
            svgo: true,
            svgoConfig: {
              plugins: [
                { name: 'preset-default', params: { overrides: { removeViewBox: false } } },
                'removeDimensions'
              ]
            },
            titleProp: true
          }
        })
      ]
    })
  3. Add TypeScript types (project root)
    // env.d.ts
    /// <reference types="vite/client" />
    declare module '*.svg?react' {
      import * as React from 'react'
      const Component: React.FC<React.SVGProps<SVGSVGElement> & { title?: string }>
      export default Component
    }
  4. Import & use
    import CheckIcon from './icons/check.svg?react'
    
    export function Button() {
      return (
        <button className="inline-flex items-center gap-2 text-blue-600 hover:text-blue-700">
          <CheckIcon className="w-5 h-5" aria-hidden="true" />
          Save
        </button>
      )
    }

    Theming: because the SVG paints with currentColor, the icon inherits the button's text color. Override it with a utility class such as text-rose-600 or an inline style={{ color: '#2563eb' }}.

Next.js: configure SVGR and import SVG components

  1. Install
    npm i -D @svgr/webpack
    # (Next already includes SVGO; we'll keep viewBox and remove width/height)
  2. Configure next.config.js
    /** @type {import('next').NextConfig} */
    const nextConfig = {
      webpack(config) {
        // Let SVGR handle .svg imports as React components
        config.module.rules.push({
          test: /\.svg$/i,
          issuer: /\.[jt]sx?$/,
          use: [{
            loader: '@svgr/webpack',
            options: {
              svgo: true,
              svgoConfig: {
                plugins: [
                  { name: 'preset-default', params: { overrides: { removeViewBox: false } } },
                  'removeDimensions'
                ]
              },
              titleProp: true
            }
          }]
        })
        return config
      }
    }
    module.exports = nextConfig
  3. Add TypeScript types (project root)
    // svg.d.ts
    declare module '*.svg' {
      import * as React from 'react'
      const Component: React.FC<React.SVGProps<SVGSVGElement> & { title?: string }>
      export default Component
    }
  4. Import & use
    import CheckIcon from '@/icons/check.svg'
    
    export default function Page() {
      return (
        <main className="p-6 text-emerald-600">
          <h1 className="text-xl font-semibold flex items-center gap-2">
            <CheckIcon className="w-6 h-6" aria-hidden="true" />
            Profile updated
          </h1>
        </main>
      )
    }

Default vs named export, and the ?react query

Most "Element type is invalid" errors, and icons that render as a broken image, come from mixing two SVGR conventions. Which one applies depends on the integration.

// vite-plugin-svgr
import Star from './star.svg?react'   // React component (default export)
import starUrl from './star.svg'      // URL string, for <img src={starUrl} />

// Create React App (@svgr/webpack, named export)
import { ReactComponent as Star } from './star.svg'  // component
import starUrl from './star.svg'                     // URL

Rule of thumb: ?react plus a default import in Vite, { ReactComponent } in a Create React App project. Copying a CRA import into a Vite app, or the reverse, is the pitfall. The ?react query is the convention in current vite-plugin-svgr (v4); older majors transformed .svg imports via the named ReactComponent export, no query.

Tree-shaking & project structure

When an SVG sprite beats components

SVGR is the right default when you theme icons individually and tree-shake them per route. It stops scaling in two cases. When the same icon repeats many times on a page (a status glyph on every table row), each instance inlines the full SVG into the DOM. And when you ship hundreds of distinct icons that mostly render on first paint, those inlined paths add up in the HTML or the bundle. An SVG sprite defines each symbol once and every use references it with <use href="#id">, so the geometry is stored once and each icon on the page is a single short element. If that matches your usage, build one with Create an SVG sprite from icons.

Troubleshooting

Related guides

Start Making SVG Icons Today with IconVectors

Download the fully-functional 30‑Day Free Trial and unlock your icon design workflow.

Version 1.70 for Windows, macOS, and Linux