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.
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.
react-svg: Loads SVGs Dynamically at Runtimereact-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 Timereact-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 Renderedreact-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.
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.
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.
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.
react-svg-loader)react-svg (unnecessary network request)react-svg-pan-zoom (no interactivity needed)import HomeIcon from './home.svg'; // compiled by SVGR
<HomeIcon className="nav-icon" />
react-svgreact-svg-loader (requires build-time knowledge)react-svg-pan-zoom alone (it doesn’t load anything)<ReactSVG src={`https://cdn.example.com/icons/${user.role}.svg`} />
react-svg-pan-zoom + inline SVG (from SVGR or hand-coded)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-svgandreact-svg-pan-zoomrequires careful coordination of injection timing. Often simpler to preload the SVG or use SVGR if the asset is static.
| Package | Purpose | Runtime Load? | Build Tool Needed? | Interactive? | Status |
|---|---|---|---|---|---|
react-svg | Inject external SVGs as inline | ✅ Yes | ❌ No | ❌ No | Maintained |
react-svg-loader | Compile SVG → React component | ❌ No | ✅ Webpack | ❌ No | Deprecated |
react-svg-pan-zoom | Add pan/zoom to existing SVG | ❌ No | ❌ No | ✅ Yes | Maintained |
react-svg — but sanitize inputs if from untrusted sources.react-svg-pan-zoom on top of inline SVGs (preferably from SVGR).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.
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.
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.
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.
A React component that injects SVG into the DOM.
Background | Basic Usage | Live Examples | API | Installation | FAQ | License
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.
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" />)
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"
/>
⚠️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:
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:
MIT