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>inonMounted; SSR renders a sized<div>preserving aspect-ratio - ThumbHash placeholder —
thumbhashprop on VImage auto-decodes to PNG data URL; supports alpha channel; better quality than BlurHash;--thumbhashflag in CLI generates hashes at build time - LQIP blur-up —
data:image/…;base64,…string asplaceholder; blurred preview withfilter: blur(); cross-fades via CSSopacitytransition - Average-color placeholder —
placeholderMode="color"derives a solid background color from the ThumbHash header (0 bytes, no canvas); or setplaceholderColordirectly - Shimmer placeholder —
placeholderMode="shimmer"shows an animated CSS skeleton (no hash needed); respectsprefers-reduced-motion - Client-side encoders —
encodeThumbHash()/encodeBlurhash()produce a hash from aFile/Canvas/ImageDatain the browser, for instant UGC previews; dependency-free
Component — VImage
- srcset autogeneration — pass
widths: [400, 800, 1200];srcsetstring built automatically;sizesprop passed through - Density descriptors —
densities: [1, 2, 3](reusesrc) or{ 1: …, 2: … }(distinct files per density) for1x/2x/3xsrcset on fixed-size images - Focal point —
focal: { x, y }maps toobject-positionso the subject stays in frame whenfit="cover"crops - WebP / AVIF switching —
srcas{ avif?, webp?, fallback }renders<picture>with typed<source>elements - Responsive art direction — named breakpoints map to
<source media="...">elements;max-widthandmin-widthqueries sorted correctly fetchpriorityprop —highfor LCP images,lowfor below-the-fold; maps to the native HTML attributedecodingprop —async(default) /sync/auto; passed directly to<img>- Error retry —
maxRetriesprop with exponential backoff; automatically retries failed loads without manual intervention - Error state —
#errorslot for custom fallback UI; built-in default (grey rectangle + icon);@errorevent
Loading
- IntersectionObserver lazy loading — IO instead of
loading="lazy"for precise control; configurablerootMarginandthreshold; SSR-safe - IO pooling — components sharing the same
rootMargin+thresholdconfig share oneIntersectionObserverinstance; no overhead at 50+ images - Background-image directive —
v-lazy-imgsetsbackground-imageon any element after viewport entry; LQIP placeholder; configurabletransition;onLoad/onErrorcallbacks useBackgroundImage()— composable for lazy + responsive (image-set()) backgrounds with blur-up; thesrcsetcapabilityv-lazy-imglacks
Composables & utilities
useImage()— headless state machine (idle → loading → loaded | error) + computedimgAttrs; works with any markupuseImagePreloader()— preload a batch of URLs before navigation;{ loaded, total, progress, isComplete, errors }buildSizes()— buildsizesattribute from breakpoint-keyed object; integrates with plugin breakpointsgeneratePreloadLink()— generates<link rel="preload" as="image">HTML for SSR/NuxtuseHead
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 --watchmode,--dry-run,--skip-existing,--concurrency; config viavue-image-kit.config.jssharpas optional peer dependency — not included in the browser bundle
Ecosystem
- Nuxt module —
vue-image-kit/nuxt; auto-registers<VImage>andv-lazy-img; auto-imports all composables and utilities; breakpoints viaruntimeConfig - Vite plugin —
vue-image-kit/vite; runs the CLI processor onbuildStart; re-runs inhandleHotUpdateduring dev; build-time imports via?vik/?thumbhashquery suffixes - Vue plugin —
app.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
npm install vue-image-kitPeer dependency:
npm install vue@>=3.0Quick start — Vue 3
1. Register the plugin
// 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
<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
<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
// 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
<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.