Diagnosing v-img When the Image Never Renders (or the Placeholder Never Shows)
v-img fails silently: no image, no placeholder, sometimes zero height. A five-minute diagnostic path — Network tab, Vue DevTools props, computed size — plus fixes tied to each finding.
19 May 2026, 03:29 UTC

You dropped a <v-img> into your Vuetify layout, pointed it at an image, and got… nothing. No image, no placeholder, sometimes not even a broken-image icon. The frustrating part is that v-img fails quietly: it renders a wrapper div and swaps content based on load state, so a failure often looks like an empty hole in the page rather than an obvious error.
The useful takeaway: almost every "v-img is broken" report is one of six causes, and you can identify which one in under five minutes by checking the Network tab, Vue DevTools, and the element's computed size — in that order.
Recognizing the condition
Typical symptoms:
- The
v-imgelement exists in the DOM but has zero height, so nothing is visible. - The image area stays blank or shows the background color indefinitely.
- The placeholder (skeleton or spinner) never appears, or appears and never goes away.
- The image loads when you open the URL directly in a browser tab, but not inside the component.
Cause and diagnostic table
| Symptom | Likely cause | First check |
|---|---|---|
| Element has 0×0 size | No width/height/aspect-ratio set; parent has no height | Inspect computed size in DevTools |
| No network request fires | src is empty, undefined, or not reactive | Vue DevTools → component props |
| Request fires, 404/500 | Wrong URL, missing asset, wrong base path | Network tab status code |
| Request blocked or red in console | CORS, mixed content (https page → http image), or auth required | Console error text |
| No placeholder during load | No placeholder slot/prop provided | Template inspection |
| Placeholder only appears on scroll | lazy-src/lazy loading defers everything until near viewport | Check for lazy-src prop |
Ordered checks
1. Verify the asset independently of Vuetify
Open DevTools → Network, filter by Img, and reload. If there is no request for your URL at all, the problem is the src binding, not the network. If there is a request, note the status code. Then paste the URL directly into a browser tab. If it fails there too, Vuetify is not your problem — fix the asset, path, or server first.
2. Inspect the src binding
In Vue DevTools, select the v-img component and look at the src prop. Common findings:
srcis an empty string because the data property hasn't been populated yet (async fetch race).- A typo in the property name, e.g.
:src="item.thubnail", silently yieldsundefined. - The value is a relative path that resolves differently once the app is deployed under a sub-path.
3. Check the rendered dimensions
v-img renders a container whose size comes from CSS, not from the image file. If neither the component nor its parent has a height, the container collapses to zero and the loaded image is invisible. In DevTools, hover the element and check the computed box. If it is 0px tall, that is your bug regardless of whether the image loaded.
4. Check placeholder and lazy configuration
If you never see a placeholder, confirm you actually provided one. Without a placeholder slot (or lazy-src), v-img shows nothing while loading and nothing after an error. Also note that with lazy loading, the placeholder only appears when the element approaches the viewport — an off-screen image will look "not loaded" by design.
5. Rule out CORS and authentication
An image URL that works when pasted into a browser tab can still fail inside your app if it requires cookies/auth headers your app's request context doesn't send, or if the page is https and the image is http (mixed content blocking). The console will usually show a specific error for these. Note that a dev-server proxy can mask this class of failure locally — retest against the real deployed URL.
Fixes tied to findings
Zero-size container: give the component explicit dimensions or an aspect ratio:
<v-img :src="item.imageUrl" height="200" cover />
<!-- or -->
<v-img :src="item.imageUrl" aspect-ratio="16/9" />Empty or async src: guard the render until data arrives:
<v-img v-if="item.imageUrl" :src="item.imageUrl" height="200" />
<v-skeleton-loader v-else type="image" />No placeholder feedback: add a placeholder slot so loading and error states are visible:
<v-img :src="item.imageUrl" height="200"
@error="imageFailed = true">
<template #placeholder>
<div class="d-flex align-center justify-center fill-height">
<v-progress-circular indeterminate />
</div>
</template>
</v-img>The @error handler is worth adding during debugging even if you remove it later — it makes silent failures observable.
Isolation test: temporarily swap in a known public image URL (for example, a placeholder image service). If the known URL renders, your component config is fine and the asset or its delivery is at fault. If it also fails, the problem is dimensions, binding, or lazy behavior.
Limitations and version notes
Prop and slot names above match Vuetify 3; Vuetify 2 is similar but check the docs for your exact version before copying. The aspect-ratio prop accepts a number or string ratio in v3. Behavior of lazy loading depends on browser IntersectionObserver support, which is universal in current browsers but worth knowing for embedded webviews.
When to escalate beyond the component
Stop debugging v-img and look elsewhere when:
- The known-good public URL also fails with correct dimensions — suspect a global CSS rule (e.g.
img { display: none }or a zero-height flex parent) or a broken Vuetify installation. - All requests fail with CORS or 401/403 — this is a server/CDN configuration issue, not a frontend one.
- The component works in isolation but fails inside
v-carousel,v-dialog, or a virtual scroller — the parent's rendering lifecycle is the suspect.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.