# Unoptimized images in Vanilla JS

> How to fix unoptimized images in Vanilla JS — with the exact fix and copy-paste code.

_Category: Performance · Detector `unoptimized-images` · Severity: error_

Images are usually the heaviest thing on a page, and when they ship without `width`/`height` they shift the layout as they load, when they ship at 2400px in a 400px slot they waste most of what they download, and when they load eagerly below the fold they steal bandwidth from what’s actually visible. AI scaffolds paste raw `<img src>` tags with none of the attributes that make images fast.

## The fix for Vanilla JS

Author the full attribute set by hand and pre-process images (squoosh, sharp, or a CDN) to the displayed size and a modern format.

```html
<img src="/hero-800.webp" width="800" height="450" loading="lazy" decoding="async" alt="Product dashboard" />
```

### Steps

1. Add `width` and `height` attributes matching the file’s real aspect ratio
2. Add `loading="lazy"` to images below the fold (never to the LCP image)
3. Resize and re-encode to WebP/AVIF at the displayed size, with `srcset` for density

## How VibeCheck detects it

The `unoptimized-images` detector flags this live in the browser and reports it to the widget's Problems list — and to your coding agent over MCP.

- **Issue string:** `hero.png (2400×1200) missing width/height`
- **Threshold:** Natural size > 2× rendered size, or declared aspect ratio off by > 0.15, or missing dimensions/lazy/alt

## FAQ

### Should I lazy-load every image?

No — never lazy-load your LCP (largest, above-the-fold) image; that delays the most important paint. Lazy-load only images below the fold. In Next.js, mark the hero with `priority`.

### What does "oversized" mean exactly?

VibeCheck flags an image whose natural (file) dimensions are more than twice its rendered (displayed) size — you’re downloading at least 4× the pixels you show. Resize to the displayed size, 2× for retina.

### Why does missing `width`/`height` cause layout shift?

Without dimensions the browser doesn’t know the image’s size until it downloads, so it reserves no space and everything below jumps when the image arrives. The attributes let it reserve the box up front.

See the general, framework-agnostic fix: https://vibecheck.wcgw.fun/fix/unoptimized-images.md

---

Fix guide from VibeCheck — https://vibecheck.wcgw.fun/fix/unoptimized-images. Full site index for LLMs: https://vibecheck.wcgw.fun/llms.txt
