Skip to content

vue-image-kit

A complete image optimization toolkit for Vue 3. One <VImage> component handles lazy loading, WebP/AVIF format switching, responsive art direction, Blurhash and LQIP placeholders, automatic srcset generation, error retry with exponential backoff, and smooth CSS transitions — with zero external runtime dependencies and a small, tree-shakeable footprint (see Bundle size & peer dependencies).

Everything you need beyond the component is included: a CLI that processes images at build time (resize, convert, generate LQIP and BlurHash, write a TypeScript manifest), CDN URL builders for 12 providers (Cloudinary, imgix, Bunny, Sanity, Storyblok, Contentful, Vercel, Cloudflare, ImageKit, TwicPics, Netlify, Gumlet) with hostname auto-detection, a Nuxt 3 module with auto-imports, a Vite plugin (including on-demand dev serving), a self-hosted on-demand image server for when there's no CDN, and headless composables for fully custom markup.

Fully typed with TypeScript. Tree-shakeable (sideEffects: false). SSR-safe — renders a native <img loading="lazy"> on the server, activates IntersectionObserver and canvas after hydration.

Features

Placeholders

  • Blurhash placeholder — custom in-house decoder (no external packages); renders to <canvas> in onMounted; SSR renders a sized <div> preserving aspect-ratio
  • ThumbHash placeholderthumbhash prop on VImage auto-decodes to PNG data URL; supports alpha channel; better quality than BlurHash; --thumbhash flag in CLI generates hashes at build time
  • LQIP blur-updata:image/…;base64,… string as placeholder; blurred preview with filter: blur(); cross-fades via CSS opacity transition
  • Average-color placeholderplaceholderMode="color" derives a solid background color from the ThumbHash header (0 bytes, no canvas); or set placeholderColor directly
  • Shimmer placeholderplaceholderMode="shimmer" shows an animated CSS skeleton (no hash needed); respects prefers-reduced-motion
  • Client-side encodersencodeThumbHash() / encodeBlurhash() produce a hash from a File/Canvas/ImageData in the browser, for instant UGC previews; dependency-free

Component — VImage

  • srcset autogeneration — pass widths: [400, 800, 1200]; srcset string built automatically; sizes prop passed through
  • Density descriptorsdensities: [1, 2, 3] (reuse src) or { 1: …, 2: … } (distinct files per density) for 1x/2x/3x srcset on fixed-size images
  • Focal pointfocal: { x, y } maps to object-position so the subject stays in frame when fit="cover" crops
  • WebP / AVIF switchingsrc as { avif?, webp?, fallback } renders <picture> with typed <source> elements
  • Responsive art direction — named breakpoints map to <source media="..."> elements; max-width and min-width queries sorted correctly
  • fetchpriority prophigh for LCP images, low for below-the-fold; maps to the native HTML attribute
  • decoding propasync (default) / sync / auto; passed directly to <img>
  • Error retrymaxRetries prop with exponential backoff; automatically retries failed loads without manual intervention
  • Error state#error slot for custom fallback UI; built-in default (grey rectangle + icon); @error event

Loading

  • IntersectionObserver lazy loading — IO instead of loading="lazy" for precise control; configurable rootMargin and threshold; SSR-safe
  • IO pooling — components sharing the same rootMargin+threshold config share one IntersectionObserver instance; no overhead at 50+ images
  • Background-image directivev-lazy-img sets background-image on any element after viewport entry; LQIP placeholder; configurable transition; onLoad/onError callbacks
  • useBackgroundImage() — composable for lazy + responsive (image-set()) backgrounds with blur-up; the srcset capability v-lazy-img lacks

Composables & utilities

  • useImage() — headless state machine (idle → loading → loaded | error) + computed imgAttrs; works with any markup
  • useImagePreloader() — preload a batch of URLs before navigation; { loaded, total, progress, isComplete, errors }
  • buildSizes() — build sizes attribute from breakpoint-keyed object; integrates with plugin breakpoints
  • generatePreloadLink() — generates <link rel="preload" as="image"> HTML for SSR/Nuxt useHead

CDN adapters — vue-image-kit/cdn

  • Zero-dependency URL builders for Cloudinary, imgix, Bunny CDN, Sanity, Storyblok, Contentful, Vercel, Cloudflare Images, ImageKit.io, TwicPics, Netlify Image CDN, Gumlet
  • Unified .url(path, options) / .srcset(path, widths) interface across all providers
  • autoLoader() — detects 8 of the 12 providers straight from a URL's hostname, no per-image adapter wiring; unrecognized hosts pass through unchanged

CLI — npx vue-image-kit generate

  • Resize images to multiple widths, convert to WebP/AVIF, generate LQIP base64, encode BlurHash
  • Write a TypeScript manifest (images.ts) with all metadata pre-computed
  • --watch mode, --dry-run, --skip-existing, --concurrency; config via vue-image-kit.config.js
  • sharp as optional peer dependency — not included in the browser bundle

Ecosystem

  • Nuxt modulevue-image-kit/nuxt; auto-registers <VImage> and v-lazy-img; auto-imports all composables and utilities; breakpoints via runtimeConfig
  • Vite pluginvue-image-kit/vite; runs the CLI processor on buildStart; re-runs in handleHotUpdate during dev; build-time imports via ?vik / ?thumbhash query suffixes
  • Vue pluginapp.use(VImageKitPlugin, { breakpoints }) registers component and directive globally
  • Zero external runtime dependencies — only Vue 3 as peer dep; full ESM + CJS, tree-shakeable, sideEffects: false (see Bundle size & peer dependencies)

Installation

bash
npm install vue-image-kit

Peer dependency:

bash
npm install vue@>=3.0

Quick start — Vue 3

1. Register the plugin

ts
// main.ts
import { createApp } from 'vue'
import { VImageKitPlugin } from 'vue-image-kit'
import App from './App.vue'

const app = createApp(App)
app.use(VImageKitPlugin)
app.mount('#app')

2. Use the component

vue
<template>
  <VImage
    src="/photo.jpg"
    alt="Mountain landscape"
    :width="1200"
    :height="600"
    blurhash="LEHV6nWB2yk8pyo0adR*.7kCMdnj"
  />
</template>

<VImage> is registered globally by the plugin. No import needed.

3. Or import explicitly

vue
<script setup lang="ts">
import { VImage } from 'vue-image-kit'
</script>

<template>
  <VImage src="/photo.jpg" alt="My photo" />
</template>

Quick start — Nuxt 3

1. Add the module to nuxt.config.ts

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

2. Use in pages and components — everything is auto-imported

vue
<template>
  <VImage
    :src="{ avif: '/hero.avif', webp: '/hero.webp', fallback: '/hero.jpg' }"
    alt="Hero image"
    :width="1920"
    :height="1080"
    :widths="[640, 1024, 1920]"
    sizes="100vw"
    blurhash="LEHV6nWB2yk8pyo0adR*.7kCMdnj"
    :lazy="true"
  />
</template>

<VImage>, v-lazy-img, and all composables are registered automatically — no imports needed. Canvas and IntersectionObserver are activated only on the client — no hydration mismatch.