Incorrect images path in production build - Vue.js

I'm building my project with Vue.js 3, Vite.js. The app works fine when in dev mode (when using the dev server). Once I do launch the build command, Vite creates for me the /dist directory containing the build for my app. If I run the preview command (vite preview) it starts with no problem the preview of my build.

The problem is with some images which are coming from Vue components. All the images of my project are in the src/assets directory.

.
├── Attribution.txt
├── README.md
├── index.html
├── package-lock.json
├── package.json
├── postcss.config.js
├── public
│   └── favicon.ico
├── src
│   ├── App.vue
│   ├── assets
│   │   ├── Temp.png
│   │   ├── bridge.png
│   │   ├── city-security.png
│   │   ├── credit-card.png
│   │   ├── deforestation.png
│   │   ├── eagle.png
│   │   ├── favicon
│   │   │   ├── android-chrome-192x192.png
│   │   │   ├── android-chrome-512x512.png
│   │   │   ├── apple-touch-icon.png
│   │   │   ├── favicon-16x16.png
│   │   │   ├── favicon-32x32.png
│   │   │   └── favicon.ico
│   │   ├── global-goals.png
│   │   ├── hall.png
│   │   ├── italian-flag.png
│   │   ├── machine-learning.png
│   │   ├── modern-architecture.png
│   │   ├── moon-festival.png
│   │   ├── people.png
│   │   ├── planet-earth.png
│   │   ├── police.png
│   │   ├── teamwork.png
│   │   ├── uk-flag.png
│   │   └── virtual.png
│   ├── components
│   │   ├── Button.vue
│   │   ├── Card.vue
│   │   ├── Column.vue
│   │   ├── Footer.vue
│   │   ├── MainContent.vue
│   │   ├── Navbar.vue
│   │   └── components-it
│   │       ├── Card-it.vue
│   │       ├── Footer-it.vue
│   │       └── Navbar-it.vue
│   ├── main.js
│   ├── router
│   │   └── index.js
│   ├── tailwind.css
│   └── views
│       ├── CitySecurity.vue
│       ├── Contribute.vue
│       ├── Credits.vue
│       ├── Goals.vue
│       ├── Home.vue
│       ├── It
│       │   ├── CitySecurity-it.vue
│       │   ├── Contribute-it.vue
│       │   ├── Credits-it.vue
│       │   ├── Goals-it.vue
│       │   ├── Home-it.vue
│       │   └── Municipality-it.vue
│       └── Municipality.vue
├── tailwind.config.js
└── vite.config.js

The images in the views have the correct path and are displayed correctly in the build. But the images in the components folder have a problem: the path doesn't change in the build file.

i.e.

<template>
  <Navbar />
  <div class="container flex flex-col items-center py-20 font-bold mx-auto">
    <h1 class="uppercase text-3xl">A new way to live the City</h1>
    <img src="../assets/hall.png" alt="Town Hall" width="250" class="py-10" />
    <p class="lg:w-1/2 text-justify leading-relaxed">
      To improve the municipality livings being we tought about some ideas, that
      are somehow able to improve the qol(Quality of life). One of the most
      reliable problem that requires to be solved is the minimization of
      burocracy, tryna to make things digital any sort of procedure. One of ours
      ideas is to improve the mechanism of shifting around the city, by giving
      cityzens public and shareble veichles to reduce pollution and the waste of
      fuel.
    </p>
    <div class="grid lg:grid-cols-3 lg:gap-0 gap-10 place-items-center pt-20">
      <div class="card container flex flex-col items-center justify-center">
        <h1 class="uppercase text-xl">Digitalization</h1>
        <img
          class="py-5"
          src="../assets/virtual.png"
          alt="Digitalization"
          width="100"
        />
        <p class="lg:w-full text-justify lg:w-3/5 lg:leading-relaxed w-3/4">
          Burocracy is so annoying, so we tought about how we can semplify it.
          The Answer is... DIGITALIZATION! Everything's simpler when digital, so
          our city is going to have a system for all of them.
        </p>
      </div>
      <div class="card container flex flex-col items-center justify-center">
        <h1 class="uppercase text-xl">Infrastructures Upgrading</h1>
        <img
          class="py-5"
          src="../assets/bridge.png"
          alt="Digitalization"
          width="100"
        />
        <p class="lg:w-full text-justify lg:w-3/5 lg:leading-relaxed w-3/4">
          To make our city better, our city is going to invest in
          infrastructures to let cityzens live their life much better. Better
          "bridges" makes better people.
        </p>
      </div>
      <div class="card container flex flex-col items-center justify-center">
        <h1 class="uppercase text-xl">Innovative Learning</h1>
        <img
          class="py-5"
          src="../assets/Machine-Learning.png"
          alt="Digitalization"
          width="100"
        />
        <p class="lg:w-full text-justify lg:w-3/5 lg:leading-relaxed w-3/4">
          Our city is going to offer a learning system to make everyone learn
          about technologies and new innovation. The mind of a man is the most
          precious part of him.
        </p>
      </div>
    </div>
  </div>
  <Footer />
</template>

<script>
import Navbar from "../components/Navbar.vue";
import Footer from "../components/Footer.vue";
import Button from "../components/Button.vue";
export default {
  name: "Municipality",
  components: {
    Navbar,
    Footer,
    Button,
  },
};
</script>

<style></style>

This is one of my views. In the build, all the img tags will have the correct path (Ex: /assets/imgName.randomNumbersAndString.png).

But this doesn't happen in the components.

i.e. - Card.vue component

<template>
  <div
    class="card container flex flex-col items-center justify-center pt-20 pb-20"
  >
    <a :href="`${link}`">
      <img
        :class="`${imgClass || 'py-5'}`"
        :src="`./src/assets/${imgName}`"
        :alt="`${imgAlt}`"
        width="100"
      />
    </a>
    <p class="lg:w-full lg:w-3/5 lg:leading-relaxed w-3/4">{{ text }}</p>
  </div>
</template>

<script>
export default {
  name: "Card",
  props: {
    link: String,
    imgClass: String,
    imgName: String,
    imgAlt: String,
    text: String,
  },
};
</script>

<style scoped></style>

Credits.vue view

<template>
  <Navbar />
  <h1 class="uppercase lg:text-5xl text-2xl mx-auto text-center pt-20">
    Images and Icons attributions
  </h1>
  <div class="grid lg:grid-cols-3 place-items-center lg:pb-10 mx-auto">
    <Card
      link="Hall icons created by Smashicons - Flaticon"
      text="Hall icons created by Smashicons - Flaticon"
      imgAlt="Hall vector representation"
      imgName="hall.png"
    />
    <Card
      link="https://www.flaticon.com/free-icons/moon-festival"
      text="Pokemon icons created by Roundicons Freebies - Flaticon"
      imgAlt="Temp logo"
      imgName="Temp.png"
    />
    <Card
      link="https://www.flaticon.com/free-icons/moon-festival"
      text="Moon festival icons created by Flat Icons - Flaticon"
      imgAlt="Moon vector representation"
      imgName="moon-festival.png"
    />
    <Card
      link="https://www.flaticon.com/free-icons/digital"
      text="Digital icons created by Freepik - Flaticon"
      imgAlt="Digital vector representation"
      imgName="virtual.png"
    />
    <Card
      link="https://www.flaticon.com/free-icons/tower-bridge"
      text="Tower bridge icons created by Freepik - Flaticon"
      imgAlt="Bridge vector representation"
      imgName="bridge.png"
    />
    <Card
      link="https://www.flaticon.com/free-icons/machine-learning"
      text="Machine learning icons created by Flat Icons - Flaticon"
      imgAlt="Machine learning vector representation"
      imgName="machine-learning.png"
    />
    <Card 
      link="https://www.flaticon.com/free-icons/business-and-finance"
      text="Business and finance icons created by Freepik - Flaticon"
      imgAlt="City security vector"
      imgName="city-security.png"
    />
    <Card 
      link="https://www.flaticon.com/free-icons/credit-card"
      text="Credit card icons created by Freepik - Flaticon"
      imgAlt="Credit card vector"
      imgName="credit-card.png"
    />
    <Card 
      link="https://www.flaticon.com/free-icons/deforestation"
      text="Deforestation icons created by Freepik - Flaticon"
      imgAlt="Deforestation vector representation"
      imgName="deforestation.png"
    />
    <Card 
      link="https://www.flaticon.com/free-icons/eagle"
      text="Eagle icons created by Freepik - Flaticon"
      imgAlt="Eagle vector"
      imgName="eagle.png"
    />
    <Card 
      link="https://www.flaticon.com/free-icons/modern-architecture"
      text="Modern architecture icons created by Freepik - Flaticon"
      imgAlt="Modern architecture vector"
      imgName="modern-architecture.png"
    />
    <Card 
      link="https://www.flaticon.com/free-icons/environment"
      text="Environment icons created by Freepik - Flaticon"
      imgAlt="Earth planet vector"
      imgName="planet-earth.png"
    />
  </div>
  <div class="container mx-auto flex flex-row items-center justify-center text-center">
    <Card 
      link="https://www.flaticon.com/free-icons/police"
      text="Police icons created by Freepik - Flaticon"
      imgAlt="Police officer vector"
      imgName="police.png"
    />
    <Card 
      link="https://www.flaticon.com/free-icons/collaboration"
      text="Collaboration icons created by Freepik - Flaticon"
      imgAlt="Community vector"
      imgName="teamwork.png"
    />
  </div>
  <Footer />
</template>

<script>
import Card from "../components/Card.vue";
import Navbar from "../components/Navbar.vue";
import Footer from "../components/Footer.vue";
import Button from "../components/Button.vue";

export default {
  name: "Credits",
  components: {
    Navbar,
    Footer,
    Button,
    Card,
  },
};
</script>

<style scoped></style>

In this case, when I pass the imgName to the Card component, in the build file the path of the image is ./src/assets/name.png.

How can I fix?


Solution 1:

The problem is, that you are passing file names as a property to the components. The full image path in the component would then be derived from both the path and the variable file name. Such dynamic paths are however not possible because all import paths need to be defined during the build time. With your component, you could theoretically pass e.g. a string from a remote server or a random string as file name, so Vite has no chance to find these images during build time.

The solution to this is very simple though: Instead of passing partial paths (file names) to the component, just pass the imported image directly as a property. The image will be automatically resolved to an absolute file path which can be resolved by Vite.

Here is an example:

<template>
  <Card :image="Teamwork" />
</template>

<script>
import Teamwork from '../../assets/teamwork.png';

export default {
  setup: () => {
    return { Teamwork };
  }
};
</script>

Using the options API, you can also pass the image via data or computed to the template. With TypeScript you would also need to define the shim for images so that you can import images.