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-svg0883313 kB32 months agoMIT
react-svg-loader0641-487 years agoMIT
react-svg-pan-zoom06961.02 MB412 years 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

This component uses @tanem/svg-injector to fetch an SVG from a given URL and inject its markup into the DOM (why?). Fetched SVGs are cached, so multiple uses of the same SVG only require a single 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. Supports fetchable URLs (relative or absolute), data:image/svg+xml URLs (URL-encoded or base64), and SVG sprite sheets via fragment identifiers (e.g. sprite.svg#icon-star). See the data URL example and sprite usage example.
  • 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. When set, a unique id is added to the <desc> element and aria-describedby is set on the SVG for assistive technology. 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. When enabled, IDs on IRI-addressable elements (clipPath, linearGradient, mask, path, etc.) are made unique, and all references to them (presentation attributes, href/xlink:href, inline style attributes, and <style> element text) are updated. Note: all matching element types are renumerated, not only those inside <defs>. Set to false if you need to query injected elements by their original IDs.
  • title - Optional String used for SVG <title> element content. If a <title> exists it will be replaced, otherwise a new <title> is created. When set, a unique id is added to the <title> element and aria-labelledby is set on the SVG for assistive technology. 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

$ 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 its core behaviour to @tanem/svg-injector, which requires a parent node when swapping in the SVG element. The swap 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:

Can I use data URIs or inline SVG strings?

data:image/svg+xml URLs are supported (both URL-encoded and base64-encoded). The underlying library parses the SVG content directly from the data URL using DOMParser, without making a network request. This is useful when bundlers like Vite inline small SVGs as data URIs. See the data URL example for details.

Inline SVG strings (raw markup passed directly as the src prop) are not supported. If you already have the SVG markup as a string (for example, a dynamically generated chart), consider parsing it with DOMParser and appending the result yourself, or rendering it with dangerouslySetInnerHTML. These approaches avoid the fetch step entirely and will also avoid the brief flash that occurs when react-svg re-injects on src change.

Security note: inserting SVG strings into the DOM bypasses React's built-in sanitisation and can expose your application to XSS if the content is not trusted. If the SVG originates from user input or a third party, sanitise it first with a library like DOMPurify before inserting it into the page.

License

MIT