react-svg vs react-svg-loader vs react-svg-pan-zoom
Working with SVGs in React: Rendering, Loading, and Interaction
react-svgreact-svg-loaderreact-svg-pan-zoomSimilar Packages:
Working with SVGs in React: Rendering, Loading, and Interaction

react-svg, react-svg-loader, and react-svg-pan-zoom are npm packages that help developers integrate SVG content into React applications—but they solve very different problems. react-svg dynamically loads and injects external SVG files as inline React components, enabling full DOM access and styling. react-svg-loader is a Webpack loader (not a React component) that converts SVG files into React components at build time. react-svg-pan-zoom provides interactive pan-and-zoom functionality for SVG content already rendered in the DOM, ideal for maps, diagrams, or technical illustrations.

Npm Package Weekly Downloads Trend
3 Years
Github Stars Ranking
Stat Detail
Package
Downloads
Stars
Size
Issues
Publish
License
react-svg244,813878263 kB1310 months agoMIT
react-svg-loader137,488641-487 years agoMIT
react-svg-pan-zoom90,2456951.02 MB41a year agoMIT

Working with SVGs in React: When to Use react-svg, react-svg-loader, or react-svg-pan-zoom

SVGs are powerful for resolution-independent graphics in web apps, but integrating them into React isn’t always straightforward. The three packages — react-svg, react-svg-loader, and react-svg-pan-zoom — address distinct stages of the SVG lifecycle: runtime loading, build-time compilation, and user interaction. Let’s break down how they differ technically and when each makes sense.

📥 Loading Strategy: Runtime vs Build Time

react-svg: Loads SVGs Dynamically at Runtime

react-svg fetches an SVG file from a URL and injects it directly into the DOM as inline markup. This lets you treat the SVG like any other React subtree — apply CSS, attach event listeners, or even modify elements via refs.

// Using react-svg
import ReactSVG from 'react-svg';

function App() {
  return (
    <ReactSVG
      src="/assets/diagram.svg"
      wrapper="span"
      className="svg-wrapper"
      afterInjection={(error, svg) => {
        if (!error) console.log('SVG injected:', svg);
      }}
    />
  );
}

This is useful when SVG sources aren’t known ahead of time (e.g., user-uploaded icons or CMS-managed illustrations). But it comes with a cost: network latency, potential XSS risks if loading untrusted SVGs, and no tree-shaking.

react-svg-loader: Compiles SVGs to React Components at Build Time

react-svg-loader is not a React component — it’s a Webpack loader. You configure it in webpack.config.js, and during the build, it transforms .svg files into React components.

// webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /\.svg$/,
        use: ['babel-loader', 'react-svg-loader']
      }
    ]
  }
};

Then in your code:

// The SVG becomes a React component
import MyIcon from './icon.svg';

function Header() {
  return <MyIcon style={{ fill: 'blue' }} />;
}

However, this package is deprecated. Its GitHub repo is archived, and the npm page recommends migrating to SVGR. New projects should avoid it entirely.

react-svg-pan-zoom: Assumes SVG Is Already Rendered

react-svg-pan-zoom doesn’t load or compile SVGs — it wraps an existing inline SVG and adds pan/zoom behavior.

import Viewer from 'react-svg-pan-zoom';

function InteractiveMap() {
  return (
    <Viewer width={800} height={600}>
      <svg width="2000" height="1500">
        {/* Your SVG content here */}
        <circle cx="100" cy="100" r="50" fill="red" />
      </svg>
    </Viewer>
  );
}

It requires the SVG to be part of your JSX already — whether hand-coded, imported via SVGR, or injected via react-svg. It adds no loading logic.

⚙️ Technical Capabilities and Limitations

Styling and Interactivity

  • react-svg: Once injected, the SVG is part of the DOM. You can style it with CSS-in-JS, global styles, or inline style props passed via wrapperProps. Event handlers can be attached to child elements using standard React techniques (though you may need to use afterInjection to add them).

  • react-svg-loader: Produces a pure React component, so you can pass props like className, style, or even onClick directly. Full control, zero runtime cost.

  • react-svg-pan-zoom: Doesn’t interfere with styling — your SVG retains all its original classes and attributes. However, mouse/touch events on SVG elements may be captured by the pan-zoom handler unless you explicitly disable interaction on certain layers.

Bundle Impact and Performance

  • react-svg: Adds ~5–10 KB to your bundle (depending on version) and incurs a network request per SVG. Not ideal for performance-critical icons.

  • react-svg-loader: Zero runtime cost — the SVG becomes part of your JavaScript bundle. But if you have many large SVGs, your JS bundle grows. Also, since it’s deprecated, tooling support is frozen.

  • react-svg-pan-zoom: Adds significant weight (~30+ KB minified) because it includes gesture detection, matrix math, and UI controls. Only justified when interactivity is required.

🛑 Deprecation and Modern Alternatives

Critical note: react-svg-loader is deprecated. The official GitHub repository (github.com/boopathi/react-svg-loader) is archived, and the npm page states: “This project is no longer maintained. Please use @svgr/webpack instead.”

For build-time SVG-to-React conversion, use SVGR:

// webpack.config.js with SVGR
{
  test: /\.svg$/,
  use: ['@svgr/webpack']
}

SVGR is actively maintained, supports TypeScript, Jest, and Next.js out of the box, and offers more configuration options.

🧪 Real-World Decision Guide

Scenario 1: You Have Static Icons Known at Build Time

  • Use SVGR (not react-svg-loader)
  • ❌ Avoid react-svg (unnecessary network request)
  • ❌ Avoid react-svg-pan-zoom (no interactivity needed)
import HomeIcon from './home.svg'; // compiled by SVGR
<HomeIcon className="nav-icon" />

Scenario 2: You Load SVGs from a CDN or API at Runtime

  • Use react-svg
  • ❌ Don’t use react-svg-loader (requires build-time knowledge)
  • ❌ Don’t use react-svg-pan-zoom alone (it doesn’t load anything)
<ReactSVG src={`https://cdn.example.com/icons/${user.role}.svg`} />

Scenario 3: You Need Users to Zoom Into a Floor Plan or Diagram

  • Use react-svg-pan-zoom + inline SVG (from SVGR or hand-coded)
  • ⚠️ You could combine with react-svg if the SVG is dynamic, but ensure the SVG is fully loaded before initializing the viewer
// If using react-svg + pan-zoom together
function MapViewer({ svgUrl }) {
  const viewerRef = useRef();

  return (
    <ReactSVG
      src={svgUrl}
      afterInjection={() => {
        // Re-render or trigger viewer update if needed
      }}
    >
      {({ ReactComponent }) => (
        <Viewer ref={viewerRef} width={800} height={600}>
          <ReactComponent />
        </Viewer>
      )}
    </ReactSVG>
  );
}

⚠️ Warning: Combining react-svg and react-svg-pan-zoom requires careful coordination of injection timing. Often simpler to preload the SVG or use SVGR if the asset is static.

📊 Summary Table

PackagePurposeRuntime Load?Build Tool Needed?Interactive?Status
react-svgInject external SVGs as inline✅ Yes❌ No❌ NoMaintained
react-svg-loaderCompile SVG → React component❌ No✅ Webpack❌ NoDeprecated
react-svg-pan-zoomAdd pan/zoom to existing SVG❌ No❌ No✅ YesMaintained

💡 Final Recommendation

  • For static SVGs: Skip all three and use SVGR.
  • For dynamic SVG loading: Use react-svg — but sanitize inputs if from untrusted sources.
  • For interactive exploration: Use react-svg-pan-zoom on top of inline SVGs (preferably from SVGR).
  • Never start a new project with react-svg-loader — it’s obsolete.

Choose based on when your SVG content is available (build vs runtime) and what users need to do with it (view vs explore). Mixing concerns leads to over-engineering; keep the solution aligned with the actual requirement.

How to Choose: react-svg vs react-svg-loader vs react-svg-pan-zoom
  • react-svg:

    Choose react-svg when you need to load SVG files from URLs at runtime and manipulate their contents via React (e.g., applying dynamic styles, attaching event handlers, or modifying elements). It’s ideal for applications where SVG assets are fetched dynamically or managed outside the build pipeline. Avoid it if your SVGs are static and known at build time—using inline components would be more efficient.

  • react-svg-loader:

    Choose react-svg-loader only if you’re using Webpack and want to pre-compile SVG files into optimized React components during the build process. This approach gives you tree-shakable, inline SVGs with no runtime overhead. However, note that this package is effectively deprecated; modern projects should use SVGR (@svgr/webpack) instead, which offers better maintenance, TypeScript support, and framework compatibility.

  • react-svg-pan-zoom:

    Choose react-svg-pan-zoom when you need users to interactively explore large or detailed SVGs by panning and zooming—common in dashboards, floor plans, or engineering diagrams. It works on any existing inline SVG and adds gesture controls without altering the original markup. Don’t use it if you only need static SVG display; it adds unnecessary complexity and bundle weight for non-interactive use cases.

README for react-svg

react-svg

npm version build status coverage status npm downloads minzipped size

A React component that injects SVG into the DOM.

Background | Basic Usage | Live Examples | API | Installation | FAQ | License

Background

Let's say you have an SVG available at some URL, and you'd like to inject it into the DOM for various reasons. This module does the heavy lifting for you by delegating the process to @tanem/svg-injector, which makes an AJAX request for the SVG and then swaps in the SVG markup inline. The async loaded SVG is also cached, so multiple uses of an SVG only require a single server request.

Basic Usage

import { createRoot } from 'react-dom/client'
import { ReactSVG } from 'react-svg'

const container = document.getElementById('root')
const root = createRoot(container)
root.render(<ReactSVG src="svg.svg" />)

Live Examples

API

Props

  • src - The SVG URL.
  • afterInjection(svg) - Optional Function to call after the SVG is injected. svg is the injected SVG DOM element. If an error occurs during execution it will be routed to the onError callback, and if a fallback is specified it will be rendered. Defaults to () => {}.
  • beforeInjection(svg) - Optional Function to call just before the SVG is injected. svg is the SVG DOM element which is about to be injected. If an error occurs during execution it will be routed to the onError callback, and if a fallback is specified it will be rendered. Defaults to () => {}.
  • desc - Optional String used for SVG <desc> element content. If a <desc> exists it will be replaced, otherwise a new <desc> is created. Defaults to '', which is a noop.
  • evalScripts - Optional Run any script blocks found in the SVG. One of 'always', 'once', or 'never'. Defaults to 'never'.
  • fallback - Optional Fallback to use if an error occurs during injection, or if errors are thrown from the beforeInjection or afterInjection functions. Can be a string, class component, or function component. Defaults to null.
  • httpRequestWithCredentials - Optional Boolean indicating if cross-site Access-Control requests for the SVG should be made using credentials. Defaults to false.
  • loading - Optional Component to use during loading. Can be a string, class component, or function component. Defaults to null.
  • onError(error) - Optional Function to call if an error occurs during injection, or if errors are thrown from the beforeInjection or afterInjection functions. error is an unknown object. Defaults to () => {}.
  • renumerateIRIElements - Optional Boolean indicating if SVG IRI addressable elements should be renumerated. Defaults to true.
  • title - Optional String used for SVG <title> element content. If a <title> exists it will be replaced, otherwise a new <title> is created. Defaults to '', which is a noop.
  • useRequestCache - Optional Use SVG request cache. Defaults to true.
  • wrapper - Optional Wrapper element types. One of 'div', 'span' or 'svg'. Defaults to 'div'.

Other non-documented properties are applied to the outermost wrapper element.

Example

<ReactSVG
  afterInjection={(svg) => {
    console.log(svg)
  }}
  beforeInjection={(svg) => {
    svg.classList.add('svg-class-name')
    svg.setAttribute('style', 'width: 200px')
  }}
  className="wrapper-class-name"
  desc="Description"
  evalScripts="always"
  fallback={() => <span>Error!</span>}
  httpRequestWithCredentials={true}
  loading={() => <span>Loading</span>}
  onClick={() => {
    console.log('wrapper onClick')
  }}
  onError={(error) => {
    console.error(error)
  }}
  renumerateIRIElements={false}
  src="svg.svg"
  title="Title"
  useRequestCache={false}
  wrapper="span"
/>

Installation

⚠️This library depends on @tanem/svg-injector, which uses Array.from(). If you're targeting browsers that don't support that method, you'll need to ensure an appropriate polyfill is included manually. See this issue comment for further detail.

$ npm install react-svg

UMD builds are also available for use with pre-React 19 via unpkg:

For the non-minified development version, make sure you have already included:

For the minified production version, make sure you have already included:

FAQ

Why are there two wrapping elements?

This module delegates it's core behaviour to @tanem/svg-injector, which requires the presence of a parent node when swapping in the SVG element. The swapping in occurs outside of React flow, so we don't want React updates to conflict with the DOM nodes @tanem/svg-injector is managing.

Example output, assuming a div wrapper:

<div> <!-- The wrapper, managed by React -->
  <div> <!-- The parent node, managed by @tanem/svg-injector -->
    <svg>...</svg> <!-- The swapped-in SVG, managed by @tanem/svg-injector -->
  </div>
</div>

See:

Related issues and PRs:

License

MIT