This article is written for Vue 2 and Nuxt 2.
If you are looking for the Nuxt 3/Vue 3 version of this article, please follow this link to the updated Nuxt 3 / Vue 3 version.
Importing images from the
Imagine a component called Doggos that should show an image of a cute puppy. I mean, we all love puppies, don’t we?
The only thing our components needs is a template with a single image tag pointing to the path. Ideally by utilizing an alias:
<img src="@/assets/doggos/riley.jpg">
(with an alias for the source directory)
<img src="../assets/doggos/riley.jpg">
(relative path without alias)
But what if we have a list of cute puppies and the user can decide which image to display on the page?
A common attempt to load a dynamic image source in Vue or Nuxt is to utilize Vue’s binding system. Imagine we have a small component set up where we can select a puppy:
<template>
<div>
<div>
<label v-for="doggo in dogNames" :key="doggo" style="margin-right: 2rem">
<input type="radio" :value="doggo" v-model="selectedDog">
{{ doggo }}
</label>
</div>
<!-- Here should be the dog image -->
</div>
</template>
<script>
export default {
data () {
return {
selectedDog: "",
dogNames: ["Riley", "Annie", "Marvin"]
}
}
}
</script>
All that is left to do is to retrieve the correct image for the
<img :src="`../assets/doggos/${selectedDog.toLowerCase()}.jpg`" :alt="selectedDog">
Let’s add that line quickly and see what happens when we push the button mapped to Riley…
Bummer, a broken image and only the alt tag! Let us take a look at the DOM. It contains the following image tag:
<img src="../assets/doggos/riley.jpg" alt="Riley">
What does that mean?
It means that the asset path hasn’t been replaced. It is the string that the expression in our template string above evaluates to.
If we move our dog images into the
Content in the
While it is sometimes necessary, for example for preview images which need a fixed URL, it brings no benefits for our use case. What if we want to swap out the image for Marvin when he grew up a little? We might hit caching issues. Let’s dig a bit deeper and find the root cause instead of going for the “quick fix” which will consume more time on the long run. You likely know that pattern
All single file components (with the
Webpack will import static assets like
After being processed, our initial image tag example
createElement('img', {
attrs: {
src: require('../assets/doggos/riley.jpg'), // this is now a module request
alt: 'Riley'
}
})
The image path has now been replaced with a Webpack module request. This works fine for static assets because their paths are known at build time. When it comes to dynamic content, including Vue’s
What can we do then?
If we take a closer look at the compiled code of our simple image tag with the static source attribute, we can find out what we need to solve the issue.
To tell Webpack which images should be loaded, we have to issue a module request on our own. This is done by calling
<img :src="require(`../assets/doggos/${selectedDog.toLowerCase()}.jpg`)" :alt="selectedDog">
Instead of binding the
We can now extract that nasty part into an own computed property for better readability.
export default {
// ...
computed: {
dogImage () {
if (!this.selectedDog) {
return
}
const fileName = this.selectedDog.toLowerCase()
return require(`../assets/doggos/${fileName}.jpg`) // the module request
}
}
}
It is very important that you are as strict as possible when it comes to the possible image file name. If we use the code from above, the final build will include every image with a
That happens because webpack cannot guess which of the images will actually be used at runtime, so it includes them all to prevent errors. The less strict you are, the more files will match and will be included in the bundle by webpack, leading to a larger bundle size.
We solved it! With
Nowadays, responsive images are a must-have on your website. They don’t only save bandwidth but also time of your users and are good for SEO. But how do we use
Almost the same way we do with the normal
<template>
<img :srcset="this.dogSourceset" sizes="(max-width: 600px) 480px, 800px" :src="dogImage">
</template>
<script>
export default {
// ...
computed: {
dogSourceset () {
const baseName = this.selectedDog.toLowerCase()
return `${require(`@/assets/img/${this.baseName}_480.jpg`)} 480w, ${require(`@/assets/img/${this.baseName}_800.jpg`)} 800w`
},
dogImage () { /* ... */ }
}
}
</script>
There we go! A working image with srcset definition. And that’s not the end: You could even go further and create
Loading images with dynamic paths isn’t that difficult if one knows what’s going on behind the scenes. By using
If you want some further reading, I suggest checking out the vue-loader docs and the Webpack documentation.
Still have questions? No problem, tweet me at @TheAlexLichter, reach out on the Vue/Nuxt Discord or write me a mail (blog at lichter dot io).
I really hope you’ve enjoyed that article! If you know some people who also have trouble with dynamic image loading in Vue or Nuxt, I’d gladly ask you to spread the word and help them out!

I'm Alex, a German web engineering consultant and content creator. Helping companies with my experience in TypeScript, Vue.js, and Nuxt.js is my daily business.
More about meSSR comes with certain caveats, including no access to APIs like the local storage on server-side. But what if you could enable SSR only for pages where SEO is needed and use the "traditional" SPA mode elsewhere? You can! Learn how in this article.
Server Side Rendering does have some limitations, such as the inability to access platform-based APIs like local storage on the server side. However, what if you could activate SSR exclusively for pages that need SEO and employ the classic single-page application (SPA) mode for other pages? Discover how to achieve this in the following article.