Support for assets images (solution included)
Problem
By default, nuxt/image does not work with images imported from the assets directory, neither locally nor in production.
However, image imports are crucial for building a quality website.
Imported images are processed by the build system, and a hash is assigned to them upon copying. A hash in the image filename is essential for proper caching mechanisms, both on the browser side and the web server side. Otherwise, if a file changes but retains the same name, users will not receive the updated content. Another benefit of importing images is the ability to work with various Vite plugins (which is also relevant in my case). Others might have additional reasons.
Currently, nuxt/image strongly resists using imported images from assets:
- Locally, it won’t find images because they reside under the
/_nuxt/path, which is served by the Nuxt server. There is no corresponding folder in thepublicdirectory. - In production, the
/_nuxtdirectory is created insidepublic, so server-side rendering works, and the<NuxtImg>component even seems to provide correct paths to the browser. But during hydration, the relative link is replaced by an absolute URL including the domain name, causingNuxtImgto stop processing it and fall back to a non-optimized version.
Solution
I found a way to solve these issues and make nuxt/image work with the assets directory. This solution works well for me and is quite straightforward.
In the component, I simply import images from assets:
<script lang="ts" setup>
import image from '~/assets/image.jpg';
</script>
<template>
<NuxtImg :src="image" format="webp" />
</template>
To make Nuxt locally serve the processed images, I add the corresponding alias in the Nuxt Image configuration:
export default defineNuxtConfig({
modules: ['@nuxt/image'],
image: {
alias: { '_nuxt/assets': 'http://localhost:3000/_nuxt/assets' },
domains: ['http://localhost:3000'],
},
});
If needed, you can limit these settings to only apply in development mode.
To address the domain replacement issue after the build, I extend the existing ipx provider by adding my own custom logic:
export default defineNuxtConfig({
modules: ['@nuxt/image'],
image: {
providers: {
ipx: {
name: 'ipx',
provider: '~/image-provider.ts',
},
},
alias: { '_nuxt/assets': 'http://localhost:3000/_nuxt/assets' },
domains: ['http://localhost:3000'],
},
});
And in the image-provider.ts file:
import type { ProviderGetImage } from '@nuxt/image';
import { getImage as ipxProvider } from '#image/providers/ipx';
export const getImage: ProviderGetImage = (src, options, ctx) => {
if (/^https?:\/\//.test(src))
src = src.replace(useRuntimeConfig().public.origin, '');
return ipxProvider(src, options, ctx);
};
Here, the public.origin config variable stores the site’s domain, but you can use any other approach to remove the domain portion.
Altogether, this method achieves the desired result, and nuxt/image works perfectly with files imported from the assets directory.
Proposal
The solution I described above is not complicated, yet it provides the significant advantages mentioned earlier.
I believe such behavior should be included in nuxt/image by default.
Let _nuxt/assets be used by Nuxt during local development, and it could be automatically configured in the alias by default (taking the --host and --port parameters into account).
In production, we could check in the browser environment: if the image’s domain matches the site’s domain, strip it out.
(This logic could be hidden behind a provider configuration option.)
All in all, this would introduce a fantastic new feature in the nuxt/image module.
What do you think about all this?
im looking for methods which can extract from assets similar to vue's <img src="..." /> behavior
possibly can be modified to tap into @vue/compiler-sfc via vite-plugin-vue to look for NuxtImg and compile asset URL dynamically
https://github.com/vuejs/core/blob/ba391f5fdf5d84bfacaca6a2a3e7057fc99efa34/packages/compiler-sfc/src/template/transformAssetUrl.ts#L37-L47