Image Kit
A complete image optimization toolkit for Vue 3. One <VImage> component handles lazy loading, WebP/AVIF format switching, responsive art direction, HazeHash, 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 HazeHash/BlurHash, write a TypeScript manifest), reports how images are used across the project, and fills in missing placeholders for existing <VImage> usages, 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 with no extra element at all, connects IntersectionObserver and paints the active placeholder on the client after hydration.
Features
Placeholders
- HazeHash placeholder — the preferred one —
hazehashprop on<VImage>: a 7–48 byte string that decodes to a blurred preview with the right aspect ratio and alpha channel, with lower perceptual error than BlurHash and ThumbHash at the same size; wins overblurhash,thumbhashandplaceholder; the build tools make it by default when the optional peerhazehashis installed - Blurhash placeholder — custom in-house decoder (no external packages); decoded into an in-memory canvas (never attached to the DOM) and turned into a CSS
background-imageon the element itself — no separate<canvas>, no wrapper - 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; set as a CSS background on the same element that goes on to show the photo, instantly covered once it loads; the opt-infadeInprop softens the whole box's appearance - Average-color placeholder —
placeholderMode="color"derives a solid background color from the ThumbHash header (0 bytes, no decode work); or setplaceholderColordirectly - Shimmer placeholder —
placeholderMode="shimmer"shows an animated CSS skeleton (no hash needed); respectsprefers-reduced-motion - Placeholders manifest — a
src → { hazehash, blurhash, thumbhash, color, width, height }map registered once (app.use(VImageKitPlugin, { placeholders })or the Nuxt module'splaceholdersoption); every<VImage>picks up its placeholder and size bysrc, no per-usage props - Client-side encoders —
encodeThumbHash()/encodeBlurhash()produce a hash from aFile/Canvas/ImageDatain the browser, for instant UGC previews; dependency-free
Component — VImage
- No wrapper element — renders as a single element (a placeholder before loading, the real
<img>/<picture>once it starts), soclass/style/data-*always land on the real image, not an intermediate<span> - 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 — everything sharing the same
rootMargin+thresholdconfig, including thev-lazy-imgdirective, shares 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 }useBreakpoints(),useLazyLoad()— the lower-level composablesVImageitself is built on, exposed for fully custom markup;useBlurhash()solves a related but different job — a live<canvas>ref for anyone who wants one directly (VImageitself builds its background placeholder a different way, see Blurhash Placeholders)useNetworkAware()— reactive save-data/connection-type statebuildSizes()— buildsizesattribute from breakpoint-keyed object; integrates with plugin breakpointsgeneratePreloadLink()— generates<link rel="preload" as="image">HTML for SSR/NuxtuseHead
CDN adapters — @macrulez/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 unchangeddetectCdnProvider()— names the provider a URL belongs to, or returnsnull
CLI — npx vue-image-kit
generate— resize images to multiple widths, convert to WebP/AVIF, generate LQIP base64, encode HazeHash/BlurHash; write a TypeScript manifest (images.ts) with all metadata pre-computed;--watchmode,--dry-run,--skip-existing,--concurrencyscan— a usage report: every<VImage>,v-lazy-img,useImage()/useBackgroundImage()in the project, the source of each image (local import,public/, CDN with provider, remote,?vik, dynamic), prop statistics and warnings;table/json/md/csvoutput,--fail-onfor CIplaceholders— HazeHash, BlurHash, ThumbHash or dominant color plus size for every<VImage>without a placeholder, from local files,public/and (with--remote) CDN/remote URLs; delivered through the placeholders manifest or written into the template- Config via
vue-image-kit.config.js;sharpas optional peer dependency — not included in the browser bundle
Ecosystem
- Nuxt module —
@macrulez/vue-image-kit/nuxt; auto-registers<VImage>andv-lazy-img; auto-imports every composable and utility; breakpoints viaruntimeConfig; can also register the self-hosted server as a real Nitro route - Vite plugin —
@macrulez/vue-image-kit/vite; runs the CLI processor onbuildStart; re-runs inhandleHotUpdateduring dev; build-time imports via?vikand placeholder query suffixes (?placeholder=color,blurhash,?color,?size,?preview, …); optional on-demand dev serving - Self-hosted on-demand server —
@macrulez/vue-image-kit/server; a small framework-agnostic Node request handler for when there's no CDN and a build step isn't wanted - 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)