Skip to content

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 — hazehash prop 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 over blurhash, thumbhash and placeholder; the build tools make it by default when the optional peer hazehash is 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-image on the element itself — no separate <canvas>, no wrapper
  • ThumbHash placeholder — thumbhash 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-up — data:image/…;base64,… string as placeholder; set as a CSS background on the same element that goes on to show the photo, instantly covered once it loads; the opt-in fadeIn prop 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 set placeholderColor directly
  • Shimmer placeholder — placeholderMode="shimmer" shows an animated CSS skeleton (no hash needed); respects prefers-reduced-motion
  • Placeholders manifest — a src → { hazehash, blurhash, thumbhash, color, width, height } map registered once (app.use(VImageKitPlugin, { placeholders }) or the Nuxt module's placeholders option); every <VImage> picks up its placeholder and size by src, no per-usage props
  • Client-side encoders — encodeThumbHash() / encodeBlurhash() produce a hash from a File/Canvas/ImageData in 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), so class/style/data-* always land on the real image, not an intermediate <span>
  • srcset autogeneration — pass widths: [400, 800, 1200]; srcset string built automatically; sizes prop passed through
  • Density descriptors — densities: [1, 2, 3] (reuse src) or { 1: …, 2: … } (distinct files per density) for 1x/2x/3x srcset on fixed-size images
  • Focal point — focal: { x, y } maps to object-position so the subject stays in frame when fit="cover" crops
  • WebP / AVIF switching — src 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 prop — high for LCP images, low for below-the-fold; maps to the native HTML attribute
  • decoding prop — async (default) / sync / auto; passed directly to <img>
  • Error retry — maxRetries 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 — everything sharing the same rootMargin+threshold config, including the v-lazy-img directive, shares one IntersectionObserver instance; no overhead at 50+ images
  • Background-image directive — v-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 }
  • useBreakpoints(), useLazyLoad() — the lower-level composables VImage itself 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 (VImage itself builds its background placeholder a different way, see Blurhash Placeholders)
  • useNetworkAware() — reactive save-data/connection-type state
  • 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 — @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 unchanged
  • detectCdnProvider() — names the provider a URL belongs to, or returns null

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; --watch mode, --dry-run, --skip-existing, --concurrency
  • scan — 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/csv output, --fail-on for CI
  • placeholders — 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; sharp as optional peer dependency — not included in the browser bundle

Ecosystem

  • Nuxt module — @macrulez/vue-image-kit/nuxt; auto-registers <VImage> and v-lazy-img; auto-imports every composable and utility; breakpoints via runtimeConfig; can also register the self-hosted server as a real Nitro route
  • Vite plugin — @macrulez/vue-image-kit/vite; runs the CLI processor on buildStart; re-runs in handleHotUpdate during dev; build-time imports via ?vik and 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)