An opinionated command-line tool to combine multiple SVG files into a single file that utilizes the <symbol>
element.
- SVG optimization using SVGO
- Removal of
fill
andstroke
attributes so they may inherit from parent CSS. - (Optional) Type-safe React component export.
For many years SVGR has been the de facto solution for rendering SVGs in React apps. (Perhaps because it was bundle with Create React App.) However, after working on production-facing, high-traffic websites for many years, I've realized that importing SVGs one-by-one as React components has real performance issues, mainly:
- The SVG components can represent a large percentage of your bundled script size. (This can be confirmed by using - The SVG components can represent a large percentage of your bundled script size. (This can be confirmed by using ) .)
- When rendered, the SVG components can add a huge amount of DOM nodes to your page. Excessive DOM size can adversely affect your Lighthouse score.
This library is most useful when you have a large number of monochrome SVGs to display on a website - perhaps in multiple places on a single page - and the fill color needs to be modified. That is to say, this library is for icons. Complex SVGs are outside the concerns of this library. For those types of SVGs, I recommend creating a separate process to optimize with SVGO and to import them on an ad-hoc basis.
While stroke manipulation is possible, it is a best practice to export SVGs with "outlined strokes" so all files can be manipulated predictably.
yarn add @timhettler/symbol-store
symbol-store -i ./icons -o ./public -t ./src/components
Run symbol-store -h
for details in your terminal.
Option | Required | Description | Default |
---|---|---|---|
-i |
Y | Path containing SVG files | N/A |
-o |
N | Path to output the combined SVG | Input path |
-t |
N | Create a TypeScript file? | Output path |
-r |
N | Add random suffix to filenames | false |
-p |
N | URL to proxy SVG requests | N/A |
The SVG <use>
element does not work with cross-origin requests. If your symbol is hosted on a different domain than your application, you'll need to proxy the request. Here's an example using Next.js route handlers:
symbol-store -i ./icons -o ./public -t ./src/components -p /api/symbol-store
This will generate a React component that uses the proxy URL:
export const UseSvg = ({ node, ...props }: UseProps) => (
<svg {...props}>
<use href={`/api/symbol-store#${node}`} />
</svg>
);
You'll need to create a route handler to proxy the requests:
// app/api/symbol-store/route.ts
import { NextResponse } from "next/server";
import { PHASE_DEVELOPMENT_SERVER } from "next/constants";
export async function GET() {
const isDev = process.env.NEXT_PHASE === PHASE_DEVELOPMENT_SERVER;
const svgUrl = isDev
? "/symbolstore.svg"
: "https://cdn.mydomain.com/symbolstore.svg";
const res = await fetch(svgUrl);
const svg = await res.text();
return new NextResponse(svg, {
headers: {
"Content-Type": "image/svg+xml",
"Cache-Control": "public, max-age=31536000",
},
});
}
Since the SVG symbol file is critical for rendering icons, it's recommended to preload it to avoid render-blocking requests. This is especially important if you're using a proxy endpoint, as the request will need to complete before any icons can be displayed.
Add the preload tag to your root layout:
<head>
<link rel="preload" href="/symbolstore.svg" as="image" type="image/svg+xml" />
</head>
If you're using a proxy endpoint, preload that instead:
<head>
<link
rel="preload"
href="/api/symbol-store"
as="fetch"
crossorigin="anonymous"
/>
</head>
Note: Using
as="fetch"
instead ofas="image"
when preloading the proxy endpoint ensures the browser makes a single request that can be reused by the<use>
elements.