Use SVGs as React Components in Astro

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 }>;
}
Category Programming
Published