When an image's display dimensions don't match its natural aspect ratio, it gets stretched or squished. Faces look wrong. Products look unprofessional. Users notice.
You have an image with natural dimensions of 1200x800 (aspect ratio 1.5:1), but you're displaying it at 400x400 (aspect ratio 1:1). The browser has three choices: stretch it, squish it, or crop/contain it. Without explicit object-fit instructions, it stretches.
Lighthouse compares the displayed aspect ratio to the natural aspect ratio. If they differ by more than a couple pixels (accounting for rounding), the audit fails. It only checks images using object-fit: fill (the default) because other values intentionally override aspect ratio behavior.
// Select the image element first
const img = $0
console.log({
natural: `${img.naturalWidth}x${img.naturalHeight}`,
displayed: `${img.clientWidth}x${img.clientHeight}`,
naturalRatio: (img.naturalWidth / img.naturalHeight).toFixed(2),
displayedRatio: (img.clientWidth / img.clientHeight).toFixed(2)
})
If the ratios don't match, that's your problem.
document.querySelectorAll('img').forEach((img) => {
if (!img.naturalWidth || !img.naturalHeight)
return
if (img.clientWidth < 5 || img.clientHeight < 5)
return
const naturalRatio = img.naturalWidth / img.naturalHeight
const displayedRatio = img.clientWidth / img.clientHeight
const diff = Math.abs(naturalRatio - displayedRatio)
if (diff > 0.05) {
console.warn('Aspect ratio mismatch:', img.src, {
natural: `${img.naturalWidth}x${img.naturalHeight} (${naturalRatio.toFixed(2)})`,
displayed: `${img.clientWidth}x${img.clientHeight} (${displayedRatio.toFixed(2)})`
})
}
})
The cleanest fix: make the display dimensions match the natural aspect ratio.
<!-- Image is 1200x800 (1.5:1 ratio) -->
<!-- Bad: forcing into a square -->
<img src="/photo.jpg" style="width: 400px; height: 400px;">
<!-- Good: maintaining ratio -->
<img src="/photo.jpg" style="width: 400px; height: 267px;">
<!-- Better: let height auto-calculate -->
<img src="/photo.jpg" width="1200" height="800" style="width: 400px; height: auto;">
With width and height attributes, browsers calculate the aspect ratio automatically:
img {
max-width: 100%;
height: auto;
}
When you need a specific container size regardless of image ratio, use object-fit:
.thumbnail {
width: 200px;
height: 200px;
object-fit: cover; /* Crop to fill, maintain ratio */
}
.product-image {
width: 100%;
height: 300px;
object-fit: contain; /* Fit inside, maintain ratio, may letterbox */
}
object-fit values:
cover - Fill container, crop excess (most common for thumbnails)contain - Fit inside container, letterbox if neededfill - Stretch to fill (default, causes distortion)none - Natural size, crop if largerscale-down - Like contain but never upscales<div class="avatar">
<img src="/user.jpg" alt="User" style="width: 100%; height: 100%; object-fit: cover;">
</div>
.avatar {
width: 48px;
height: 48px;
border-radius: 50%;
overflow: hidden;
}
Let CSS maintain the ratio while the container scales:
.video-thumbnail {
width: 100%;
aspect-ratio: 16 / 9;
}
.video-thumbnail img {
width: 100%;
height: 100%;
object-fit: cover;
}
The container maintains 16:9, and object-fit: cover handles any image that doesn't match exactly.
If every image in a gallery should be square, crop them to square before uploading rather than relying on CSS:
// Server-side with Sharp
import sharp from 'sharp'
sharp('input.jpg')
.resize(400, 400, {
fit: 'cover',
position: 'attention' // Smart crop
})
.toFile('output-square.jpg')
This gives you control over what gets cropped rather than leaving it to CSS.
NuxtImg with fit prop:<template>
<NuxtImg
src="/photo.jpg"
width="400"
height="400"
fit="cover"
alt="Photo"
/>
</template>
<div class="aspect-square">
<NuxtImg src="/photo.jpg" class="w-full h-full object-cover" />
</div>
next/image with fill and objectFit:<div className="relative w-48 h-48">
<Image
src="/photo.jpg"
fill
style={{ objectFit: 'cover' }}
alt="Photo"
/>
</div>
<Image
src="/photo.jpg"
width={1200}
height={800}
style={{ width: '100%', height: 'auto' }}
alt="Photo"
/>
document.querySelectorAll('img').forEach((img) => {
const style = getComputedStyle(img)
if (style.objectFit !== 'fill')
return // Using cover/contain is fine
const naturalRatio = img.naturalWidth / img.naturalHeight
const displayedRatio = img.clientWidth / img.clientHeight
if (Math.abs(naturalRatio - displayedRatio) > 0.05) {
console.warn('Still distorted:', img.src)
}
})
width: 100% without height: auto can cause stretching if there's an inherited height.object-fit: cover or they'll oval.<img> elements. Distorted background images need background-size: cover or contain.Your homepage might look perfect while product pages stretch images into a grid. Unlighthouse checks every page and surfaces all distorted images across your entire site.
Geolocation
Stop requesting geolocation permission on page load. Users distrust sites that ask for location without context - tie requests to user actions instead.
Low Resolution Images
Serve images with appropriate resolution for the display size and device pixel ratio. Blurry images hurt perceived quality.