Skip to content

Nuxt ​

Ready-made configurations for the Nuxt module. Each recipe says when to use it, shows the complete nuxt.config.ts and the template, and states what you get. Choosing an Approach helps to pick one.

Install sharp — and thumbhash wherever a ThumbHash is computed — as dev dependencies:

bash
npm install -D sharp thumbhash

The minimal setup ​

When to use it. You only want <VImage>, v-lazy-img and the composables auto-imported, with nothing prepared ahead of time.

ts
// nuxt.config.ts
export default defineNuxtConfig({
  modules: ['@macrulez/vue-image-kit/nuxt'],
  vueImageKit: {
    breakpoints: { sm: '(max-width: 640px)', md: '(max-width: 1024px)' },
  },
})

What you get. The component and the directive are registered globally, its styles are added for you, and every composable is auto-imported. breakpoints are the named media queries used by art direction.

Blur for everything in a folder ​

When to use it. Images are shown from data (:src="item.image") or from templates, and you want a blur and the right size for all of them without touching a single template.

ts
// nuxt.config.ts
export default defineNuxtConfig({
  modules: ['@macrulez/vue-image-kit/nuxt'],
  vueImageKit: {
    placeholders: { dirs: ['public/images'] },
  },
})
vue
<template>
  <VImage v-for="item in items" :key="item.id" :src="item.image" :alt="item.title" />
</template>

What you get. The module registers the Vite plugin itself, builds a manifest from public/images on every build and gives it to every <VImage>. There is no command to run and no file to commit. Each entry is keyed by the URL the file is served at (/images/cat.jpg), and <VImage> finds it by the exact src.

During server rendering the image already has its width and height, so the page doesn't shift; the blur itself is painted in the browser after hydration. In nuxt dev the manifest follows the folder: a new or changed file triggers a reload.

Things to know. dirs are relative to the project root. The folder must be served at a fixed URL, which is true for public/ — see the next recipe for images kept elsewhere.

Images kept outside public ​

When to use it. The files are in the repository, but a template refers to them by some other URL — typically a CDN or a proxy path.

ts
vueImageKit: {
  placeholders: {
    dirs: [
      'public/images',
      { dir: 'content/photos', urlPrefix: 'https://cdn.example.com/photos' },
    ],
  },
}

What you get. content/photos/a.jpg is stored as https://cdn.example.com/photos/a.jpg — the src your template uses. Without a urlPrefix a folder outside public/ is keyed by its project path, which matches nothing in production, and the build warns about it.

Images imported through ~/assets are different: their URL is hashed when the app is built, so no manifest can know it in advance. Use the next recipe for those.

Blur for an imported image ​

When to use it. An image is imported into a component from ~/assets, and you want its blur.

ts
// nuxt.config.ts
import { vueImageKit } from '@macrulez/vue-image-kit/vite'

export default defineNuxtConfig({
  modules: ['@macrulez/vue-image-kit/nuxt'],
  vite: {
    plugins: [vueImageKit({ generate: false })],
  },
})
vue
<script setup lang="ts">
import photo from '~/assets/photo.jpg'
import placeholder from '~/assets/photo.jpg?placeholder'
</script>

<template>
  <VImage :src="photo" v-bind="placeholder" alt="Photo" />
</template>

What you get. ?placeholder is computed at build time and gives { hazehash, placeholderColor, width, height } (blurhash when the hazehash package is not installed) — the props of <VImage>. generate: false keeps the plugin from resizing anything. The imports are typed once you add /// <reference types="@macrulez/vue-image-kit/vite/client" /> to a .d.ts file of the project.

Choose the fields. A list returns exactly what you name, so one import can carry the blur, the real size and a ready-made preview. The values are plain data inlined at build time, identical on the server and in the browser, so nothing can mismatch during hydration:

vue
<script setup lang="ts">
import cover from '~/assets/cover.jpg'
import ph from '~/assets/cover.jpg?placeholder=blurhash,size'
import preview from '~/assets/cover.jpg?preview'
</script>

<template>
  <VImage :src="cover" v-bind="ph" alt="Cover" />
  <section class="hero" :style="{ backgroundImage: `url(${preview})` }" />
</template>

The fields are hazehash, blurhash, thumbhash, color, size, preview, aspect and all, and there are single-value imports too (?color, ?size, ?aspect, ?preview) and per-image settings (?blurhash&components=6x4). All of them are described in Choosing what to compute.

Typing the lists. A list or an import with settings can't be typed by the bundled declarations, so let the plugin write them. The types option belongs to the plugin you add yourself in vite.plugins; a plugin the module registers for dirs or urls doesn't take it:

ts
vite: {
  plugins: [vueImageKit({ generate: false, types: 'vik-imports.d.ts' })],
},

The file is written into the project and is picked up by the TypeScript configuration Nuxt generates; run nuxt dev or nuxt build once so it exists.

Things to know. The module registers the Vite plugin on its own only when you give it dirs or urls. For ?placeholder imports alone, add the plugin as above. If you use both, give the module dirs and skip vite.plugins — then the imports work too.

Blur for every imported image ​

When to use it. The project imports its images (import hero from '~/assets/hero.webp', or src="~/assets/hero.webp" in a template) and you want a blur for all of them without touching a single template.

ts
vueImageKit: {
  placeholders: { imports: true },
},

What you get. Every statically imported raster image gets its HazeHash (or BlurHash, when hazehash is not installed) and real size at build time, and <VImage :src="hero" /> finds them by itself — in dev, in the production build and during server rendering. The module registers the Vite plugin on its own; there is no manifest and no query to add. sharp is needed as a dev dependency.

Things to know. Imports with a query, SVG and lazy import() are skipped; imports: { exclude: ['/sprites/'] } leaves out images that are never shown through <VImage>. See A placeholder for every imported image.

A manifest file plus folders ​

When to use it. You already run npx vue-image-kit placeholders to fill in templates, and want folders on top of it.

ts
vueImageKit: {
  placeholders: {
    manifest: './image-placeholders.ts',
    dirs: ['public/images'],
  },
}

What you get. The two manifests are merged, with the file's entries winning over the folders'. A bare string, placeholders: './image-placeholders.ts', still means a file only.

Remote images ​

When to use it. Images come from a CDN or an external host.

ts
vueImageKit: {
  placeholders: {
    urls: ['https://res.cloudinary.com/demo/image/upload/sample.jpg'],
  },
}

What you get. Each URL is downloaded during the build and keyed by that exact string. A recognised CDN is fetched as a small rendition. The results are cached, so a rebuild doesn't download again.

Another kind of placeholder, and tuning ​

When to use it. You want a BlurHash or a ThumbHash instead of the default HazeHash, only a color, or a bigger HazeHash (tuning.budget).

ts
vueImageKit: {
  placeholders: {
    dirs: ['public/images'],
    mode: 'thumbhash',
    tuning: { components: [6, 4], sample: 128, color: 'average' },
  },
}

mode is 'hazehash' (default when the hazehash package is installed), 'blurhash' (default otherwise), 'thumbhash' or 'color'. tuning is described in Tuning the hashes. The same settings apply to ?placeholder imports.

Variants made on request ​

When to use it. You want resized WebP/AVIF files without a build step and without a CDN.

ts
vueImageKit: {
  onDemandServer: true,
}
vue
<VImage src="/photos/cat.jpg" alt="Photo" :widths="[400, 800]" loader="server" />

What you get. A Nitro route at /_vik/image that resizes images from public/ on the first request and caches them. See On-demand images as a Nitro route.

Variants built ahead of time ​

When to use it. You prefer generated files, built before the app is.

Nuxt runs a client and a server Vite build, so a plugin placed in vite.plugins would process the folder twice. Run the command from a script instead:

json
{
  "scripts": {
    "images": "vue-image-kit generate --input ./assets/originals --output ./public/images --widths 400,800,1200",
    "prebuild": "npm run images"
  }
}

What you get. Files in public/images and, with --manifest, a TypeScript list of them. See Generate Images for every option.