A few weeks ago, I started migrating the tech stack of this personal website from Next.js to Astro. But I encountered the first big challenge when I attempted to use SVG as React Component in Astro.
Background
In Next.js, I used @svgr/webpack
to transform SVGs to React Components.
However, in Astro, when SVG assets are imported in script files, they are processed by the Astro built-in plugin, vite-plugin-assets
vite-plugin-assets
.
This plugin converts the SVG to a string of base64 data URL as mentioned in the official Astro documentation .
import svgReference from './image.svg'; // svgReference === '/src/image.svg'
import svgReference from './image.svg'; // svgReference === '/src/image.svg'
As a result, there’s no straightforward approach of using SVGs as React Components in Astro.
Solution
Since Astro uses Vite as the bundler, an existing Vite plugin offers a solution: vite-plugin-svgr .
To optimize SVG assets, it’s also recommended to install additional svgr plugins and configurations:
yarn add -D vite-plugin-svgr@4 @svgr/plugin-svgo @svgr/plugin-jsx
yarn add -D vite-plugin-svgr@4 @svgr/plugin-svgo @svgr/plugin-jsx
Next, configure the Vite plugin in astro.config.mjs
astro.config.mjs
.
export default defineConfig({
vite: {
plugins: [
svgr({
include: '**/*.svg?react',
svgrOptions: {
plugins: ['@svgr/plugin-svgo', '@svgr/plugin-jsx'],
svgoConfig: {
plugins: ['preset-default', 'removeTitle', 'removeDesc', 'removeDoctype', 'cleanupIds'],
},
},
}),
],
},
})
export default defineConfig({
vite: {
plugins: [
svgr({
include: '**/*.svg?react',
svgrOptions: {
plugins: ['@svgr/plugin-svgo', '@svgr/plugin-jsx'],
svgoConfig: {
plugins: ['preset-default', 'removeTitle', 'removeDesc', 'removeDoctype', 'cleanupIds'],
},
},
}),
],
},
})
You might notice the ?react
?react
suffix in the include
include
option.
This suffix helps distinguish SVG assets that need to be converted to React Component.
We should therefore import SVG assets like this:
// SomeReactComponent.tsx
import LogoSvg from './logo.svg?react';
// SomeReactComponent.tsx
import LogoSvg from './logo.svg?react';
This suffix suggests that the SVG asset can be processed with various plugins for conversion into components across multiple frameworks.
For example, you can use the ?vue
?vue
suffix to transform SVGs to Vue Components within a Vue file.
This is an Amazing feature, especially in projects that use multiple frameworks.
Gotcha 💥
Astro Version
If your SVG asset is not being converted into a React Component, you should check the version of Astro you are working with.
In earlier versions, the Astro plugin vite-plugin-assets
vite-plugin-assets
took precedence, always transforming SVG assets to base64 data URLs, regardless of our specified suffix.
Thankfully, the issue was addressed in a later update, as discussed in GitHub Issue #8333 .
TypeScript
If you are using TypeScript, you might encounter an error when importing SVG assets.
You could add this code snippet to a src/files.d.ts
src/files.d.ts
file:
declare module '*.svg?react' {
import * as React from 'react';
const ReactComponent: React.FunctionComponent<React.ComponentProps<'svg'> & { title?: string }>;
export default ReactComponent;
}
declare module '*.svg?react' {
import * as React from 'react';
const ReactComponent: React.FunctionComponent<React.ComponentProps<'svg'> & { title?: string }>;
export default ReactComponent;
}
Solution
Since Astro uses Vite as the bundler, an existing Vite plugin offers a solution: vite-plugin-svgr .
To optimize SVG assets, it’s also recommended to install additional svgr plugins and configurations:
yarn add -D vite-plugin-svgr@3 @svgr/plugin-svgo @svgr/plugin-jsx
yarn add -D vite-plugin-svgr@3 @svgr/plugin-svgo @svgr/plugin-jsx
Next, configure the Vite plugin in astro.config.mjs
astro.config.mjs
.
export default defineConfig({
vite: {
plugins: [
svgr({
include: '**/*.svg?react',
svgrOptions: {
plugins: ['@svgr/plugin-svgo', '@svgr/plugin-jsx'],
svgoConfig: {
plugins: ['preset-default', 'removeTitle', 'removeDesc', 'removeDoctype', 'cleanupIds'],
},
},
}),
],
},
})
export default defineConfig({
vite: {
plugins: [
svgr({
include: '**/*.svg?react',
svgrOptions: {
plugins: ['@svgr/plugin-svgo', '@svgr/plugin-jsx'],
svgoConfig: {
plugins: ['preset-default', 'removeTitle', 'removeDesc', 'removeDoctype', 'cleanupIds'],
},
},
}),
],
},
})
You might notice the ?react
?react
suffix in the include
include
option.
This suffix helps distinguish SVG assets that need to be converted to React Component.
We should therefore import SVG assets like this:
// SomeReactComponent.tsx
import { ReactComponent as Logo } from './logo.svg?react';
// SomeReactComponent.tsx
import { ReactComponent as Logo } from './logo.svg?react';
This suffix suggests that the SVG asset can be processed with various plugins for conversion into components across multiple frameworks.
For example, you can use the ?vue
?vue
suffix to transform SVGs to Vue Components within a Vue file.
This is an Amazing feature, especially in projects that use multiple frameworks.
Gotcha 💥
Astro Version
If your SVG asset is not being converted into a React Component, you should check the version of Astro you are working with.
In earlier versions, the Astro plugin vite-plugin-assets
vite-plugin-assets
took precedence, always transforming SVG assets to base64 data URLs, regardless of our specified suffix.
Thankfully, the issue was addressed in a later update, as discussed in GitHub Issue #8333 .
TypeScript
If you are using TypeScript, you might encounter an error when importing SVG assets.
You could add this code snippet to a src/files.d.ts
src/files.d.ts
file:
declare module '*.svg?react' {
import * as React from 'react';
export const ReactComponent: React.FunctionComponent<React.ComponentProps<'svg'> & { title?: string }>;
}
declare module '*.svg?react' {
import * as React from 'react';
export const ReactComponent: React.FunctionComponent<React.ComponentProps<'svg'> & { title?: string }>;
}