Vue 3 Image Fallback Component for Failed and Missing Src
Build a vue image fallback component in Vue 3 that catches load errors, swaps in a placeholder URL, and prevents infinite onerror loop cycles in reactive templates.
Vue 3's composition API makes it straightforward to build a vue image fallback component that catches load errors, swaps in a deterministic placeholder URL, and prevents the infinite onerror loop that trips up many first implementations.
This guide covers the minimal SFC pattern, the loop-guard technique, TypeScript prop types, and how to pair the component with fallback.pics URLs that match your layout's exact dimensions and colors.
Problem
Why Vue's native img tag fails without a vue image fallback
When an image src returns a 404, the browser fires an error event and renders a broken-image icon. Vue binds src reactively, but the framework does not intercept load errors by default. If the bound value is undefined, null, or an unavailable URL, your component renders nothing useful for the user.
In a product catalog or content feed this is immediately noticeable. Layout space collapses if you have not set explicit dimensions, CLS scores rise, and there is no useful visual state. A broken-image icon does not communicate why the image is missing or indicate what should be there.
A small wrapper component solves all three problems at once. It catches the error event, replaces the failing src with a stable fallback URL, and preserves the original layout geometry. Built once and placed in your shared UI library, it handles the fallback pattern consistently across every image surface in the app.
Component
The minimal Vue fallback SFC with @error binding
The component template binds src to a reactive ref initialized with the incoming prop, then listens to the @error event. On error, the handler replaces the ref value with the fallback URL. Reactive bindings mean Vue re-renders the element automatically.
Always provide width and height attributes. Without them, the fallback image and the original image may have different intrinsic sizes, which produces a second layout shift after the swap.
<!-- FallbackImage.vue -->
<template>
<img
:src="currentSrc"
:alt="alt"
:width="width"
:height="height"
@error="handleError"
/>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue';
const props = withDefaults(defineProps<{
src: string;
fallback: string;
alt?: string;
width?: number;
height?: number;
}>(), { alt: '' });
const errored = ref(false);
const currentSrc = ref(props.src);
watch(() => props.src, (val) => {
errored.value = false;
currentSrc.value = val;
});
function handleError() {
if (!errored.value) {
errored.value = true;
currentSrc.value = props.fallback;
}
}
</script> Loop guard
Prevent infinite error loops with a boolean flag
Without the errored flag, the @error handler fires on both the original src and the fallback src. If your fallback URL is also unreachable—network is down, domain expired, or blocked by a CSP directive—the handler fires again, sets the same src again, which fires again. Most browsers will not loop indefinitely, but the behavior differs between Chrome, Firefox, and Safari.
The errored boolean stops this cleanly. Once the first error fires, set the flag and swap the src. Any subsequent error events on the same element are ignored because the condition check exits immediately. Reset the flag in the watch on props.src so a legitimate src change gets a fresh attempt.
If you want a visible error state instead of a silent placeholder swap, expose an error slot or emit a custom error event from the component. That is useful for debug tooling, analytics instrumentation, or error boundaries at the page level.
Fallback URLs
Choosing placeholder dimensions for the vue image fallback
The fallback URL should match the expected dimensions of the image slot. If your product card shows a 400x300 image, the fallback should be 400x300 so the layout stays intact when the real image fails. A 200x200 fallback in a 400x300 slot causes its own CLS.
Pass the fallback as a prop at the call site rather than hardcoding it inside the component. That keeps the component generic and lets each use case supply its own dimensions, background color, text label, and format. Different parts of the app have different slot sizes and brand requirements.
<!-- Product card usage -->
<FallbackImage
:src="product.imageUrl"
fallback="https://fallback.pics/api/v1/400x300/E5E7EB/9CA3AF?text=No+Image"
:alt="product.name"
:width="400"
:height="300"
/>
<!-- Avatar slot -->
<FallbackImage
:src="user.avatar"
fallback="https://fallback.pics/api/v1/avatar/80?text=?"
:alt="user.name"
:width="80"
:height="80"
/>
<!-- Blog thumbnail -->
<FallbackImage
:src="post.thumbnail"
fallback="https://fallback.pics/api/v1/thumbnail/1200x630?text=No+Thumbnail&theme=purple"
:alt="post.title"
:width="1200"
:height="630"
/> Composable
A reusable useImageFallback composable for Vue 3
If you need to add fallback behavior to an existing img element rather than wrapping it in a new component, a composable gives you the same logic in a composable function. Bind the returned src ref to the img element and pass the returned onError handler to @error.
The composable is also useful when working with a component library that renders its own img element internally and only exposes an image URL prop. You can preprocess the URL before passing it in, and conditionally generate the fallback URL server-side.
// composables/useImageFallback.ts
import { ref, watch } from 'vue';
export function useImageFallback(
initialSrc: Ref<string> | string,
fallbackSrc: string
) {
const src = isRef(initialSrc) ? initialSrc : ref(initialSrc);
const currentSrc = ref(src.value);
const errored = ref(false);
watch(src, (val) => {
errored.value = false;
currentSrc.value = val;
});
function onError() {
if (!errored.value) {
errored.value = true;
currentSrc.value = fallbackSrc;
}
}
return { currentSrc, onError, errored };
} Dynamic catalogs
Integration with reactive product lists and Pinia stores
In a product catalog that fetches data from an API, product image URLs may be unknown until the API response arrives. Using FallbackImage keeps the layout stable during both the loading state and the error state. During loading you can show a skeleton; after the API responds the real URL is bound, and if that URL 404s the fallback takes over seamlessly.
When the API returns products without an image field, build a computed fallback URL that encodes the product name so the placeholder is labeled. This helps QA and support staff identify which product slot the placeholder represents in screenshots and recorded sessions.
For large catalogs, all fallback URLs for the same product type can share the same dimensions and base color parameters. This means they hash to the same CDN cache entry and do not generate unique backend requests for every missing product.
<template>
<div class="grid">
<FallbackImage
v-for="product in products"
:key="product.id"
:src="product.imageUrl ?? ''"
:fallback="`https://fallback.pics/api/v1/400x300/F3F4F6/6B7280?text=${encodeURIComponent(product.name)}`"
:alt="product.name"
:width="400"
:height="300"
/>
</div>
</template> Key takeaways
What to standardize before shipping
- Bind src to a reactive ref and use an errored boolean flag to prevent the infinite onerror loop.
- Reset the errored flag in a watch on the src prop so re-fetched URLs get a fresh load attempt.
- Pass the fallback URL as a prop at the call site so dimensions and colors match each image slot.
- Use https://fallback.pics/api/v1/{w}x{h} to generate dimension-matched placeholder URLs on demand.
- Emit a custom error event or expose an error slot when you need to track fallback triggers in analytics or monitoring.
Production fallback layer
Use fallback.pics anywhere an image URL is accepted.
Start with one deterministic URL and standardize fallback behavior across your design system.