Loading & Error Handling
Error state & fallback slot
Default fallback — if no #error slot is provided, a grey rectangle with a broken-image SVG icon is shown:
<VImage src="/missing.jpg" alt="Missing" :width="400" :height="300" />
<!-- Shows: grey rectangle + SVG icon -->Custom fallback via slot:
<VImage src="/missing.jpg" alt="Missing" :width="400" :height="300">
<template #error>
<div class="error-placeholder">
<img src="/no-image.svg" alt="" />
<p>Image is currently unavailable</p>
</div>
</template>
</VImage>Handling errors in JavaScript:
<script setup lang="ts">
function handleError(e: Event) {
console.error('Image failed to load:', e)
// Report to Sentry, switch to a fallback URL, etc.
}
</script>
<template>
<VImage src="/photo.jpg" alt="Photo" @error="handleError" />
</template>Lazy loading
<VImage> uses IntersectionObserver for lazy loading — not the native loading="lazy" attribute — for full control over when loading starts.
<!-- Default: loads when the image is 200px from the viewport -->
<VImage src="/photo.jpg" alt="Photo" />
<!-- Custom rootMargin — start loading 500px before the viewport -->
<VImage src="/photo.jpg" alt="Photo" root-margin="500px" />
<!-- Load when 50% of the image is visible -->
<VImage src="/photo.jpg" alt="Photo" :threshold="0.5" />
<!-- Disable lazy loading — load immediately (above the fold) -->
<VImage src="/photo.jpg" alt="Photo" :lazy="false" />How it works
- On mount — an
IntersectionObserveris created and begins watching the wrapper element - When the element enters the viewport (accounting for
rootMargin) — the imagesrcis set and loading begins (status: 'loading') - When the image loads —
statustransitions to'loaded'; the placeholder fades out - The observer disconnects after the first intersection — no unnecessary callbacks
SSR behaviour
On the server, IntersectionObserver is unavailable. <VImage> renders a plain <img loading="lazy"> without any JavaScript-driven state. After hydration, onMounted sets up the IO as normal.
fetchpriority & decoding
Control browser prioritization and decoding strategy:
<!-- Hero image: load first, decode async -->
<VImage src="/hero.jpg" alt="Hero" :lazy="false" fetchpriority="high" decoding="async" />
<!-- Below-the-fold: deprioritize -->
<VImage src="/footer-banner.jpg" alt="Banner" fetchpriority="low" />| Prop | Type | Default | Description |
|---|---|---|---|
fetchpriority | 'high' | 'low' | 'auto' | — | Browser fetch priority hint |
decoding | 'async' | 'sync' | 'auto' | 'async' | Image decoding mode |
Error retry
Automatically retry failed image loads with exponential backoff:
<VImage src="/flaky-image.jpg" alt="Photo" :max-retries="3" :retry-delay="500" />| Prop | Type | Default | Description |
|---|---|---|---|
maxRetries | number | 0 | Max retry attempts |
retryDelay | number | 1000 | Initial delay in ms (doubles each retry) |
Network-aware loading
useNetworkAware() wraps the browser's Network Information API — saveData (the user opted into data savings) and effectiveType ('slow-2g' | '2g' | '3g' | '4g'). SSR-safe (saveData starts false on the server) and reactive to the connection's change event. Support is Chromium-only today (Firefox/Safari don't implement the API) — saveData just stays false there, so nothing breaks, it simply can't help.
import { useNetworkAware } from 'vue-image-kit'
const { saveData, effectiveType } = useNetworkAware()Two places already use it:
useImagePreloader()silently skipspreload()calls whilesaveDatais on — preloading trades bandwidth for a smoother later transition, the wrong trade once the user asked to save data.VImage'srespectSaveDataprop (opt-in, defaultfalse): whilesaveDatais on, it neutralizespriority(the image stays lazy instead of being forced eager/high-priority) and downgradessrcto the smallest URL it can actually find one for — the lowest key in adensitiesmap, or the smallestwcandidate inimage.srcset(a manifest/?vikvalue). Plainwidthshas no distinct URL to downgrade to (seegenerateSrcset— the browser negotiates via thewdescriptor against one URL, not a URL per width) so it's a no-op there.
<VImage
src="/photo.jpg"
alt="Photo"
priority
respect-save-data
:densities="{ 1: '/photo.jpg', 2: '/photo@2x.jpg' }"
/>For a direct check outside a component (e.g. before kicking off a batch preload yourself), isSaveDataEnabled() is the same check without the reactive wrapper:
import { isSaveDataEnabled } from 'vue-image-kit'
if (!isSaveDataEnabled()) {
await preload(nextSlideUrls)
}useImagePreloader
Preload a batch of images before navigation — useful for galleries and carousels.
<script setup lang="ts">
import { useImagePreloader } from 'vue-image-kit'
const { preload, progress, isComplete, errors } = useImagePreloader()
async function goToNextSlide() {
await preload(['/slide-2.jpg', '/slide-3.jpg'])
// All images are cached — transition is instant
currentSlide.value++
}
</script>
<template>
<div v-if="!isComplete">Loading {{ progress }}%…</div>
</template>