This page looks best with JavaScript enabled

Use Dynamic Images in Nuxt

 ·   ·  ☕ 3 min read

First, let me clarify what I mean by “dynamic image”.

Imagine the below situations -

  1. You are rendering a list of products and their images. The image may be stored in local folder or coming in from S3/comparable infrastructure
  2. You want to change image / make image interactive. For e.g., users select fruits and the corresponding fruit image gets added in a basket
  3. You want to preprocess the image in some shape and form at runtime. For e.g., watermarking the image

Rendering an Image

Typically, you render images with -

1
<img src="https://example.com/images/apple.png" />

This assumes that images/apple.png exists in public folder or available publicly.

Rendering a Dynamic Image

While providing images from a public location is great (& should be preferred), sometimes we need the images to be “processed” during build, or make it “dynamic” in some way or the other (e.g., watermarked on the go). I typically use assets/images for images that need not be publicly exposed - but any folder should work well.

Pointing images to the assets or some other folder is straight-forward in Nuxt2.

1
2
3
<template>
    <img :src="require(`~/assets/images/${apple.image}`)"/>
</template>

require works since Nuxt2 uses Webpack, which will get the import image dynamically and make it available for rendering on the page.

Nuxt3 and Vite do not work in the same way. There is more than one way to render dynamic images in Nuxt 3.

1. Using glob

As mentioned here, you can use glob to feed the right input to the image -

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<template>
  <img :src="fruit_name" />
</template>

<script setup>
  import { filename } from 'pathe/utils';

  const glob = import.meta.glob('~/assets/images/*.png', { eager: true });
  const images = Object.fromEntries(
    Object.entries(glob).map(([key, value]) => [filename(key), value.default])
  );

  const fruit_name = 'apple';
</script>

This option works, but is tiring to look at. There may also be performance issues if there are large number of images.

Note that the issue described here exists for local images. Remote images provide public URLs that can be rendered directly.

2. Use Vite require plugin

Bring the usability of Webpack require to Vite using vite-plugin-require plugin.

Install the plugin -

1
npm install -D vite-require-context

Change vite.config.js to add the plugin -

1
2
3
4
5
6
7
import vitePluginRequire from "vite-plugin-require";

export default {
  // ...
  plugins: [vitePluginRequire()],
  // ...
};

Nuxt (specifically NuxtImg) provides a helpful useImage composable.

You should now be able to do -

1
2
3
4
5
6
7
<template>
  <NuxtImg :src="require(fruit_image_url)" />
</template>

<script setup>
  const fruit_image_url = "~/assets/images/apple.png";
</script>

Conclusion

While both options work, do note that -

  1. Use public for images that will not change. This option is easier and likely to be more performant
  2. Else, use one of the above options based on your use case
Stay in touch!
Share on

Prashanth Krishnamurthy
WRITTEN BY
Prashanth Krishnamurthy
Technologist | Creator of Things