---
title: "Image SEO"
slug: "image-seo"
category: "seo"
tags: ["seo", "images", "alt-text", "cls", "lazy-loading", "image-sitemap"]
status: "stable"
last_updated: 2026-05-14
summary: "Filenames, alt text, dimensions, modern formats, lazy loading, image sitemaps, and rich-result thumbnails."
related: ["[[seo/page-speed]]", "[[seo/core-web-vitals]]", "[[seo/technical]]", "[[seo/og-images]]", "[[frontend/html]]", "[[frontend/accessibility]]", "[[seo/glossary]]", "[[seo/audit-checklist]]"]
---

## Overview

Images are ranking signals, layout-shift hazards, and the largest single contributor to page weight. Get the seven attributes right (filename, alt, width, height, format, loading, sitemap) and the image earns rich-result thumbnails, contributes to ranking, and stops breaking CLS. Get any one wrong and the image either fails to rank or drags the page down.

## Name image files like search queries

Filenames are read by Google Image Search and used as a secondary relevance signal. Treat them like a second alt text.

- Use kebab-case descriptive names: `postgres-replication-architecture.png`, not `IMG_4827.png` or `screenshot-final-v2.png`.
- Include the topic keyword, not the page slug. The image may be reused on multiple pages.
- Strip metadata and camera EXIF noise. `exiftool -all=` clears it.
- Folder structure matters too. `/images/postgres/replication-architecture.png` reads better than `/uploads/2026/05/file.png`.

## Write descriptive alt text, never stuff keywords

Alt text serves screen readers first and search engines second. The signals overlap; write for the screen reader.

- Describe what the image shows, not what page it lives on. "Diagram of Postgres logical replication with primary and replica" beats "Postgres image."
- 8 to 15 words is the sweet spot. Past 25 words, screen readers truncate.
- Decorative images use empty alt: `alt=""`. Do not omit the attribute; an absent alt is treated as missing.
- Never repeat the caption verbatim; the screen reader announces both.
- Never keyword-stuff: "Postgres Postgres database Postgres SQL Postgres replication" is a manual-action trigger.

## Always set `width` and `height` to prevent CLS

Cumulative Layout Shift is the cheapest Core Web Vital to fix; reserve space.

```html
<img
  src="/images/postgres-replication.avif"
  width="1200"
  height="630"
  alt="Postgres logical replication architecture"
  loading="lazy"
/>
```

- Set the intrinsic dimensions, not the rendered size. CSS resizes responsively; the attributes reserve the box.
- Or set CSS `aspect-ratio: 1200 / 630`. Either works; both is redundant.
- A late-loading image without reserved dimensions is the most common CLS killer. See the CLS rules in [[seo/core-web-vitals]].

## Ship AVIF and WebP with a JPEG fallback

Modern formats cut bytes by 30 to 70 percent at the same visual quality. Browser support is over 95% as of 2025.

```html
<picture>
  <source srcset="/images/hero.avif" type="image/avif" />
  <source srcset="/images/hero.webp" type="image/webp" />
  <img src="/images/hero.jpg" width="1200" height="630" alt="Hero" />
</picture>
```

- AVIF first: 50% smaller than WebP at the same quality, slower to encode.
- WebP second: universal support, fast encoding.
- JPEG fallback: photos. PNG fallback: graphics with hard edges.
- Compress to visually lossless. `cwebp -q 80` and `avifenc --speed 6 --min 30 --max 50` are reasonable defaults.

## Lazy-load below the fold, eager-load the LCP image

Native browser lazy loading is universal; use it.

- Every image below the fold: `loading="lazy"`.
- The LCP image (hero, first article image): `loading="eager"` plus `fetchpriority="high"` plus a `<link rel="preload">` in `<head>`.
- Iframes: `loading="lazy"` works the same way.
- Do not lazy-load images that are 100% visible on initial render; the lazy-load overhead delays LCP.

## Submit an image sitemap

Image sitemaps surface images that JavaScript-rendered pages might hide from crawlers.

```xml
<url>
  <loc>https://example.com/seo/image-seo</loc>
  <image:image>
    <image:loc>https://example.com/images/image-seo-hero.avif</image:loc>
    <image:caption>Image SEO hero diagram</image:caption>
  </image:image>
</url>
```

- Use the `xmlns:image="http://www.google.com/schemas/sitemap-image/1.1"` extension on the main sitemap.
- Include only images on indexable canonical pages.
- Regenerate on every build; never hand-edit.

## Earn rich-result thumbnails with a high-res image and matching schema

Rich results (article cards, recipe cards, video cards) require a `<meta property="og:image">` and a JSON-LD `image` field that both point at a high-resolution image.

- Minimum 1200 pixels on the long edge. Smaller images are disqualified from rich results.
- Three aspect ratios for Article: 1x1, 4x3, 16x9. Provide all three in the JSON-LD `image` array if the schema is `Article` or `BlogPosting`. See [[seo/structured-data]].
- The OG image and the JSON-LD image should match. See the OG image rules in [[seo/og-images]].

## Set explicit `Cache-Control` on image URLs

Repeat visitors and CDN edges depend on cache headers; the default is browser-dependent.

- Hashed filenames (`/images/hero-8f3a.avif`) ship with `Cache-Control: public, max-age=31536000, immutable`.
- Non-hashed filenames need a shorter TTL plus `stale-while-revalidate=86400` so updates propagate without breaking the cache hit rate.
- Set the headers at the CDN, not in the application. [[ops/cloudflare-cache-rules|Cloudflare Cache Rules]] do this with a single rule; see [[ops/cloudflare]] for the wider setup.

## Related

- [[seo/page-speed]]
- [[seo/core-web-vitals]]
- [[seo/technical]]
- [[seo/og-images]]
- [[frontend/html]]
- [[frontend/accessibility]]
- [[seo/glossary]]
- [[seo/audit-checklist]]
