nuxt: Assets with dynamic names are not resolved
Environment
- Operating System:
Linux - Node Version:
v16.14.2 - Nuxt Version:
3.0.0-rc.8 - Package Manager:
npm@7.17.0 - Builder:
vite - User Config:
- - Runtime Modules:
- - Build Modules:
-
Reproduction
https://stackblitz.com/edit/github-pq8nym?file=app.vue
Describe the bug
If you have an image referencing a dynamic asset, e.g.
<template>
<img :src="`~/assets/${dynamic_image_name}`" alt="Discover Nuxt 3" />
</template>
<script setup lang="ts">
const dynamic_image_name = 'zero-config.svg';
</script>
then this is rendered as
<img src="~/assets/zero-config.svg" alt="Discover Nuxt 3">
without correctly resolving (and copying) the image, thus it doesn’t show in the browser.
Additional context
Refs https://github.com/nuxt/framework/pull/6635.
Logs
No response
About this issue
- Original URL
- State: open
- Created 2 years ago
- Reactions: 55
- Comments: 76 (19 by maintainers)
Thanks @danielroe, this works like a charm! Do you think it makes sense to add the following helper method as a built-in composable?
Is this the only way to get dynamic images in Nuxt 3? Was super easy in Nuxt 2 with require.
I believe this is vite and @vitejs/plugin-vue behaviour. It’s a bit complicated, but you can do this with
import.meta.glob:https://stackblitz.com/edit/github-pq8nym-aoavsh
Wait for the official to provide a better solution.😅😅
i use this technique and it works with me just fine
For dynamic image use this
<img :src="`/_nuxt/assets/images/${img.src}`" alt="image"/>Note: define your directory properly, my image are saved in images folder.
It work for me 😃
Another way to do it is just use regular imports:
As great as Nuxt3 already, hurdles like this where Nuxt2 simply was better, are frustrating. I also liked to get img dimensions using require.
@danielroe does the core team have ideas on how to support these usecases or is this not a concern for the foreseeable future?
I suggest mentioning things missing from Nuxt2 to the migration guide, so people are aware of the current limitations: https://nuxt.com/docs/migration/overview
@Umer-Farooq10 this will break in prod
Interesting how we, with every “progress/update”, make the harder things simpler and the once simpler things harder. 🙂 Nuxt 2 was beautiful.
Finding it really frustrating to work with images, this needs to be properly addressed in the documentation: making it very clear that it is not possible to using images with dynamic names unless import.meta.glob is used.
It is very sad, that the best solution for using dynamic assets with Vite is not to use dynamic assets. After years with Webpack, optimizing images by the bundler seems like a basic task, but not with Vite
I’ve written a blog post about that topic recently, but the easiest way would be using the
publicfolder unless you have a good reason not to. In this case, using theimport.meta.globsolution from Daniel would be the best way. But loading assets that way is less performant than viapublicfolder.Putting “_nuxt” at the start of the image link worked for me. Like this:
<img :src="'_nuxt/assets/' + image" />This also worked for images in public.@sami-baadarani Thanks for providing the vite link for more details. Something to keep in mind with this approach: from the docs it states that:
+1 - I use the require() method to build 500 static pages using about 600 images which are inserted dynamically, a solution to this will be very much appreciated
This work for me. I use it as a Composables. Thank you 😃
I created a temporary composable function while waiting for an official solution.
It uses the above solution with good types as much as possible (I didn’t succeed to have the correct
Modulereturned byimport.meta.globfunction).Hi! You can use src without ‘~/assets’ if your remove image from assets to public directory
<img :src="/${dynamic_image_name}" alt="Discover Nuxt 3" />It works for me 😃 try it toois there any update on this? Anything official from the Nuxt team maybe? @danielroe?
When there are many images, this approach will significantly increase the initial loading size of JS files. It is urgent to find a way to import only specified resources.
@Werhww Now try to deploy 😉
Sir, I love you ❤️❤️. After literal weeks of suffering I can finally display images from the assets folder.
Someone really should put that in every Documentation out there!
I would highly recommend use with file extension, as the glob with
eagerwill end up including content of all imports within your build. Probably safe with images (includes just files) but you wouldn’t want to accidentally include anything else. (CSS files, would get included not as filenames but as actual CSS.)As for a composable, it’s an interesting idea. But I’m very cautious. I wonder if it would tree-shake out properly if not used.
can’t believe this doesn’t work
Is it fair to say that using Public folder is the best solution right now ?
Quite so. If you prefer the previous behaviour, you can of course use the Nuxt 3 webpack builder. The difference in behaviour you are referring to is the difference between webpack and vite, not between Nuxt 3 and Nuxt 2.
For client-side render this will add all imported files into
headtag with prefetch mode, which will force user to load all of the assets.Also it will put all import\resolve references into a file which can become big very quickly if you have a lot of assets.
I managed to solve this by returning a new URL using a computed function.
Store the path for your image in imagePath ref.
For more details check https://vitejs.dev/guide/assets.html#new-url-url-import-meta-url
I think having some way to improve DX and fetch dynamic images easier would be helpful indeed ☺️
I think i just found an easy solution for this problem. I used
<NuxtImg>for dynamic images. It worked both during development and in my production buildThanks to alternative solutions by @danielroe and @tobiasdiez
I ended up with following solution.
useAssetsWe can input just only image name without any relative path. The function will handle the rest for us. For example, the image.png is located in'assets/img/image.png'. We can just put'image.png'in theuseAssetsfunction.getImagesTo get list of the image and use as variable Assume that we haveimage1.pngandimage2.pngunder'/assets/img'dir.Can someone explain this solution a little ? What is the point of this import ?
import { filename } from 'pathe/utils';@netopolit
Yes, but the Vite team closes such issues since this limitation is “by design”. You should have significant weight in the community to be heard by Vite team
Is there a way from a module to inject build assets? I have a module that needs to inject svg icons to be resolved by
vite-svg-loaderin the target app, but couldn’t find anything on nuxt or nitro config@miixel2 This works fine for me. If it’s not working for you, please raise an issue with a reproduction 🙏
Thank you for your reply but that’s the whole point: I am using the public folder, see my comment up here
@BorisKamp Well, you have two options (also explained here):
So the workaround is to move the files to the assets folder and use Daniels solution?
@joelee1992 There is, as Daniel pointed out in https://github.com/nuxt/nuxt/issues/14766#issuecomment-1397365205 😊
I solved it by using the following code:
For reference, in Nuxt 2 this component worked like this:
Also interested in how to address this.
@IJsLauw
#nuxt-layer-baseis an alias for my nuxt layer. If you don’t need to use layer, you can simply use~(alias for project root).@szulcus Yeah, noticed that. Don’t do what I did.
Yeah, you’re right, technically, in the end, it’s a choice of default/out-of-the-box build/dev tools. Not to downplay the benefits, of course. It’s just devs wanting to avoid an “extra” build setup, however minute.
Your function unfortunately doesn’t distinguish between files with the same name that are nested in different folders. I changed it up a bit, but thanks for the inspiration!
I also did autocomplete. Maybe someone will like it: In
nuxt.config.ts:In
*.{ts,vue}:Surprisingly that even in official document addressed we can use following syntax but it doesn’t work.
Ref: https://nuxt.com/docs/getting-started/assets
@netopolit Indeed they will create chunks for all files in that directory. But there’s no other way to handle fully dynamic imports, because the bundler has to include everything in chunks because it doesn’t know what variable you will pass at runtime.
I can’t explain why, but this fn not working
const getImg = (img) => {return new URL('./../assets/images/useful/' + img + '.png', import.meta.url).href}and this one is working for me O_o :
const getImg = (img) => {const link = './../assets/images/useful/' + img + '.png'return new URL(link, import.meta.url).href}This works for me.
Use like below:
The solution from @antoinezanardi worked well for me (thank you!) but only in dev. With
generatethe path gets a hash appended to the filename so the files aren’t found anymore.So to make it work in both dev and prod I had to change the condition from
to
You have to be a bit careful if your filenames contain a period though, there is a chance it’ll get mistakenly matched. And if for some reason the hash gets appended differently this’ll break as well, so it’s really just a workaround.
Also FYI, there is another caveat - when a
buildAssetDiris set that is called/assets/, the approach above will break.