Import SVG files as React components in Vite with zero runtime overhead
TypeScript
0
2 commits
updated Oct 18, 2026
Import SVG files as React components in Vite with zero runtime overhead.
import ArrowLeft from "./icons/arrow-left.svg?react";
<ArrowLeft className="icon" onClick={handleClick} />;
Requires Vite ≥ 7.
// vite.config.ts
import svgee from "vite-svgee-plugin";
export default defineConfig({ plugins: [svgee()] });
Each imported SVG is typed as (props: SVGProps<SVGSVGElement>) => ReactNode. Pull in the ambient declaration for *.svg?react the same way you would vite/client - either in a .d.ts file:
/// <reference types="vite-svgee-plugin/client" />
or via compilerOptions.types in your tsconfig.json:
{ "compilerOptions": { "types": ["vite-svgee-plugin/client"] } }
svgee({
svgo: {
/* SVGO config */
}, // or false to disable. Requires `svgo` to be installed.
forwardRef: false, // wrap with React.forwardRef, forwarding ref to the root <svg>
memo: false, // wrap with React.memo, can be combined with `forwardRef`
dimensions: true, // set to false to strip width/height from the root <svg>
});
When a module ID matches *.svg?react, the plugin:
createElement callsThe SVG's original attributes are merged with props passed at the call site, so overriding className, aria-label, event handlers, etc. all work as expected.
// generated output (simplified)
import { createElement as h } from "react";
export default function SvgArrowLeft(props) {
return h("svg", { viewBox: "0 0 24 24", ...props }, h("path", { d: "..." }));
}
The XML parser (src/parse.ts) is purpose-built for SVG, handling:
&, A, A)&, < in attribute valuesA static lookup table (~200 entries) maps HTML/SVG attribute names to their React JSX equivalents.
TL;DR: on default settings svgee converts a typical icon in ~0.071 ms against svgr's ~0.38 ms — about 5× faster. With SVGO off on both sides, ~0.021 ms against ~0.33 ms — about 16×.
Each plugin converts the same four SVG files 1,000 times. We take the middle time of those runs, so one slow run cannot skew the result.
Time to convert one file, in milliseconds.
Without SVGO:
| File | svgee | svgr | speedup |
|---|---|---|---|
tiny.svg | 0.021 | 0.327 | 15.6× |
small-icon.svg | 0.021 | 0.328 | 15.6× |
medium-icons.svg | 0.086 | 0.814 | 9.5× |
stress-heavy.svg | 0.629 | 5.427 | 8.6× |
With SVGO - using the same SVGO settings:
| File | svgee | svgr | speedup |
|---|---|---|---|
tiny.svg | 0.071 | 0.381 | 5.4× |
small-icon.svg | 0.154 | 0.467 | 3.0× |
medium-icons.svg | 0.825 | 1.558 | 1.9× |
stress-heavy.svg | 6.209 | 10.078 | 1.6× |
Run it yourself:
npm run bench
MIT
Import SVG files as React components in Vite with zero runtime overhead
TypeScript
0
2 commits
updated Oct 18, 2026
Import SVG files as React components in Vite with zero runtime overhead.
import ArrowLeft from "./icons/arrow-left.svg?react";
<ArrowLeft className="icon" onClick={handleClick} />;
Requires Vite ≥ 7.
// vite.config.ts
import svgee from "vite-svgee-plugin";
export default defineConfig({ plugins: [svgee()] });
Each imported SVG is typed as (props: SVGProps<SVGSVGElement>) => ReactNode. Pull in the ambient declaration for *.svg?react the same way you would vite/client - either in a .d.ts file:
/// <reference types="vite-svgee-plugin/client" />
or via compilerOptions.types in your tsconfig.json:
{ "compilerOptions": { "types": ["vite-svgee-plugin/client"] } }
svgee({
svgo: {
/* SVGO config */
}, // or false to disable. Requires `svgo` to be installed.
forwardRef: false, // wrap with React.forwardRef, forwarding ref to the root <svg>
memo: false, // wrap with React.memo, can be combined with `forwardRef`
dimensions: true, // set to false to strip width/height from the root <svg>
});
When a module ID matches *.svg?react, the plugin:
createElement callsThe SVG's original attributes are merged with props passed at the call site, so overriding className, aria-label, event handlers, etc. all work as expected.
// generated output (simplified)
import { createElement as h } from "react";
export default function SvgArrowLeft(props) {
return h("svg", { viewBox: "0 0 24 24", ...props }, h("path", { d: "..." }));
}
The XML parser (src/parse.ts) is purpose-built for SVG, handling:
&, A, A)&, < in attribute valuesA static lookup table (~200 entries) maps HTML/SVG attribute names to their React JSX equivalents.
TL;DR: on default settings svgee converts a typical icon in ~0.071 ms against svgr's ~0.38 ms — about 5× faster. With SVGO off on both sides, ~0.021 ms against ~0.33 ms — about 16×.
Each plugin converts the same four SVG files 1,000 times. We take the middle time of those runs, so one slow run cannot skew the result.
Time to convert one file, in milliseconds.
Without SVGO:
| File | svgee | svgr | speedup |
|---|---|---|---|
tiny.svg | 0.021 | 0.327 | 15.6× |
small-icon.svg | 0.021 | 0.328 | 15.6× |
medium-icons.svg | 0.086 | 0.814 | 9.5× |
stress-heavy.svg | 0.629 | 5.427 | 8.6× |
With SVGO - using the same SVGO settings:
| File | svgee | svgr | speedup |
|---|---|---|---|
tiny.svg | 0.071 | 0.381 | 5.4× |
small-icon.svg | 0.154 | 0.467 | 3.0× |
medium-icons.svg | 0.825 | 1.558 | 1.9× |
stress-heavy.svg | 6.209 | 10.078 | 1.6× |
Run it yourself:
npm run bench
MIT