SVG on Vue 3

Manage SVG on Vue 3 / Nuxt 3

From Unsplash

SVG on Vue 3

vue
#vue #nuxt
This guide use Vue 3 / Nuxt 3 with Vite.

From github.com/jpkleemans/vite-svg-loader, you can use a specific module with Nuxt 3 if you want: github.com/gitFoxCode/nuxt-icons.

Add Node dependency

From github.com/jpkleemans/vite-svg-loader

pnpm
pnpm add vite-svg-loader -D
npm
npm install vite-svg-loader --save-dev
yarn
yarn add vite-svg-loader -D

Add to Vue 3

vite.config.ts
import svgLoader from "vite-svg-loader";

export default defineConfig({
  plugins: [
    vue(),
    svgLoader(), // https://github.com/jpkleemans/vite-svg-loader#readme
  ],
});

Add to Nuxt 3

nuxt.config.ts
import { defineNuxtConfig } from "nuxt";
import svgLoader from "vite-svg-loader";

export default defineNuxtConfig({
  vite: {
    plugins: [
      svgLoader(), // https://github.com/jpkleemans/vite-svg-loader#readme
    ],
  },
});

Add SVG for test

Create a directory like assets into your project and add a new SVG like github.svg.

- assets
  - icons
    - github.svg
- components
  - svg-icon.vue (soon)
[vite|nuxt].config.ts

Here an example of github.svg from https://icones.js.org

assets/icons/github.svg
<svg
  xmlns="http://www.w3.org/2000/svg"
  xmlns:xlink="http://www.w3.org/1999/xlink"
  aria-hidden="true"
  role="img"
  class="iconify iconify--mdi"
  width="32"
  height="32"
  preserveAspectRatio="xMidYMid meet"
  viewBox="0 0 24 24"
>
  <path
    d="M12 2A10 10 0 0 0 2 12c0 4.42 2.87 8.17 6.84 9.5c.5.08.66-.23.66-.5v-1.69c-2.77.6-3.36-1.34-3.36-1.34c-.46-1.16-1.11-1.47-1.11-1.47c-.91-.62.07-.6.07-.6c1 .07 1.53 1.03 1.53 1.03c.87 1.52 2.34 1.07 2.91.83c.09-.65.35-1.09.63-1.34c-2.22-.25-4.55-1.11-4.55-4.92c0-1.11.38-2 1.03-2.71c-.1-.25-.45-1.29.1-2.64c0 0 .84-.27 2.75 1.02c.79-.22 1.65-.33 2.5-.33c.85 0 1.71.11 2.5.33c1.91-1.29 2.75-1.02 2.75-1.02c.55 1.35.2 2.39.1 2.64c.65.71 1.03 1.6 1.03 2.71c0 3.82-2.34 4.66-4.57 4.91c.36.31.69.92.69 1.85V21c0 .27.16.59.67.5C19.14 20.16 22 16.42 22 12A10 10 0 0 0 12 2Z"
  ></path>
</svg>

Remove all attributes like class, width or height to avoid conflicts of sizing and add fill="currentColor" or stroke="currentColor" to allow CSS color on SVG.

assets/icons/github.svg
<svg
  xmlns="http://www.w3.org/2000/svg"
  xmlns:xlink="http://www.w3.org/1999/xlink"
  aria-hidden="true"
  role="img"
  preserveAspectRatio="xMidYMid meet"
  viewBox="0 0 24 24"
  fill="currentColor"
>
  <path
    d="M12 2A10 10 0 0 0 2 12c0 4.42 2.87 8.17 6.84 9.5c.5.08.66-.23.66-.5v-1.69c-2.77.6-3.36-1.34-3.36-1.34c-.46-1.16-1.11-1.47-1.11-1.47c-.91-.62.07-.6.07-.6c1 .07 1.53 1.03 1.53 1.03c.87 1.52 2.34 1.07 2.91.83c.09-.65.35-1.09.63-1.34c-2.22-.25-4.55-1.11-4.55-4.92c0-1.11.38-2 1.03-2.71c-.1-.25-.45-1.29.1-2.64c0 0 .84-.27 2.75 1.02c.79-.22 1.65-.33 2.5-.33c.85 0 1.71.11 2.5.33c1.91-1.29 2.75-1.02 2.75-1.02c.55 1.35.2 2.39.1 2.64c.65.71 1.03 1.6 1.03 2.71c0 3.82-2.34 4.66-4.57 4.91c.36.31.69.92.69 1.85V21c0 .27.16.59.67.5C19.14 20.16 22 16.42 22 12A10 10 0 0 0 12 2Z"
  ></path>
</svg>

Create component

Watch out ../assets/icons/${props.name}.svg because path is relative to my stack, if your assets directory is not on same place, update path.

components/svg-icon.vue
<script setup lang="ts">
const props = defineProps<{
  name?: string;
}>();

const currentIcon = computed(() =>
  defineAsyncComponent({
    loader: () => import(`../assets/icons/${props.name}.svg`),
    loadingComponent: {
      template: "<span></span>",
    },
    errorComponent: {
      template: "<span>error</span>",
    },
    delay: 200,
    timeout: 3000,
    suspensible: true,
  })
);
const attrs = useAttrs();
</script>

<template>
  <span>
    <component :is="currentIcon" :class="attrs.class" />
  </span>
</template>

Nuxt 3

For Nuxt 3, you have to add client-only.

components/svg-icon.vue
<template>
  <span>
    <client-only>
      <component :is="currentIcon" :class="attrs.class" />
      <template #fallback />
    </client-only>
  </span>
</template>

Usage

app.vue
<template>
  <div>
    <svg-icon name="github" class="w-6 h-6" />
  </div>
</template>
Don't use subdirectories into assets/icons, because build will not compile nested SVG, put all your icons in root of icons.