DEV Community

Sandeep Singh
Sandeep Singh

Posted on

Image SEO for Blogs: Finding Missing, Broken and Unindexable Images

Most blog posts lean on images: screenshots, diagrams, charts, product photos, hero images for social shares. And most blogs carry some broken ones that nobody has noticed. The author's browser cached the image years ago, the post looks fine to them, and a reader sees an empty box with a tiny broken-image icon.

Broken images are a quality problem first: a tutorial with missing screenshots stops being a tutorial. They are also an image SEO problem, because an image that returns a 404, or that crawlers can't find in the first place, can't show up in Google Images, Discover or the thumbnails beside text results.

This guide covers why blog images break, how to find every broken or missing image on a site, and the image SEO basics that Google documents, with sources for each.

Why blog images break

In rough order of how often I see them:

  1. Deleted media. Someone cleans up the media library and removes files still used in old posts. In WordPress, deleting a file from the library doesn't edit the posts that embed it.
  2. Domain or HTTPS moves. Images hard-coded as http://old-domain.com/wp-content/uploads/... break, or get blocked as mixed content, after a move.
  3. Hotlinked images. Images embedded straight from another site disappear when that site reorganises, deletes them, or blocks hotlinking.
  4. CDN or storage changes. Moving uploads to S3, a CDN or a new image service changes URLs. Old posts keep the old URLs.
  5. Expired third-party embeds. Screenshot services, badge generators and chart widgets shut down or change URL schemes.
  6. Case and filename mismatches. Screenshot.PNG works on one server and 404s on a case-sensitive one after a migration.
  7. Broken srcset entries. The src works, but one of the resized versions listed in srcset is missing, so only some devices see a broken image. These are the hardest to notice by eye.

Step 1: Find every broken image on the blog

Crawl the site and check image URLs

Checking pages one by one doesn't scale. A crawler collects every <img> URL across the site and requests each one.

  • Screaming Frog SEO Spider (free up to 500 URLs, limits) lists images under the Images tab and response codes for every resource. Filter for Client Error (4xx) and use Inlinks to see which posts embed each broken image.
  • Google Search Console won't list broken images directly, but its Page indexing and Core Web Vitals reports help with the related problems below.

A quick script for a single post

For spot checks, this pulls the src and every srcset candidate from a page and checks each one:

import re, sys, urllib.request, urllib.parse

page = sys.argv[1]
req = urllib.request.Request(page, headers={"User-Agent": "img-check/1.0"})
html = urllib.request.urlopen(req, timeout=20).read().decode("utf-8", "replace")

urls = set()
for tag in re.findall(r"<(?:img|source)\b[^>]*>", html, re.I):
    m = re.search(r'\ssrc="([^"]+)"', tag)
    if m:
        urls.add(m.group(1))
    s = re.search(r'\ssrcset="([^"]+)"', tag)
    if s:
        for part in s.group(1).split(","):
            if part.strip():
                urls.add(part.strip().split()[0])

for u in sorted(urls):
    if u.startswith("data:"):
        continue
    full = urllib.parse.urljoin(page, u)
    try:
        r = urllib.request.urlopen(urllib.request.Request(full, method="HEAD",
            headers={"User-Agent": "img-check/1.0"}), timeout=15)
        code = r.status
    except urllib.error.HTTPError as e:
        code = e.code
    except Exception as e:
        code = type(e).__name__
    print(code, full)
Enter fullscreen mode Exit fullscreen mode

Run it with python3 imgcheck.py https://yourblog.example/some-post/. Some servers reject HEAD requests; if you get a 405, retry that URL with GET.

Check for mixed content images

On an HTTPS page, an image loaded over http:// is mixed content. MDN explains that browsers now try to upgrade "upgradable" content such as images to HTTPS, but if the image isn't available over HTTPS the upgrade fails and the image doesn't load. MDN also notes that images in srcset or <picture> aren't upgraded and are blocked instead (MDN: Mixed content). Search your post content for src="http:// and srcset="http://. I covered the WordPress side of this in HTTPS and Mixed Content on WordPress After Installing SSL.

Step 2: Fix them

Cause Fix
File deleted, original available Re-upload to the same path, or update the post to the new path
File gone, no copy Check the Wayback Machine for an archived copy; otherwise replace or remove the image and adjust the text
Old domain or http:// URLs Search-replace in the database (on WordPress, WP-CLI search-replace handles serialized data safely)
Hotlinked image gone Host your own copy if licensing allows, or replace it
Missing srcset sizes Regenerate thumbnails or resized versions
Case mismatch Rename the file or fix the reference so they match exactly

Then re-crawl. Fixing images by hand is error-prone, and the re-crawl is what proves the fix.

Step 3: Make images indexable

A working image can still be invisible to search. Google's image SEO best practices set out the basics.

Use real <img> elements

Google says it "can find images in src attribute of <img> element (even when it's a child of other elements, such as the <picture> element)" and that it "doesn't index CSS images." A hero image set as a CSS background-image won't be indexed. If the image matters for search, use an <img>.

Always include a src fallback

Google notes that "some browsers and crawlers don't understand" srcset and <picture>, and recommends you "always specify a fallback URL using the src attribute." A <picture> element should contain an <img> with a src.

Use supported formats

Google Search supports BMP, GIF, JPEG, PNG, WebP, SVG and AVIF in <img src>, and suggests filename extensions match the file type (same source).

Consider an image sitemap

Image sitemaps tell Google about images it might not otherwise find, "such as images your site reaches with JavaScript code." Each <url> can list up to 1,000 <image:image> entries, and image URLs can be on other domains, such as a CDN.

Step 4: Alt text, filenames and context

Google is explicit: "The most important attribute when it comes to providing more metadata for an image is the alt text," which "also improves accessibility for people who can't see images." It warns against keyword stuffing in alt attributes, which "may cause your site to be seen as spam" (Google).

Practical rules for blog images:

  • Describe what's in the image in a short sentence. For a screenshot: what it shows and why it's there ("WordPress Site Health screen with the HTTPS status warning").
  • Decorative images can use an empty alt="" so screen readers skip them.
  • Image links: Google notes alt text acts as anchor text when an image is a link, so describe the destination.
  • Filenames: Google says filenames give "very light clues." wordpress-site-health-https.png beats IMG00023.PNG.
  • Placement: put images near the text they illustrate. Google uses page content and captions to understand them.
  • Same image, same URL: if an image appears on many pages, reference it with one consistent URL so it can be cached and reused.

A crawl export of <img> tags with missing alt attributes, filtered to content images, gives you a manageable to-do list.

Step 5: Images and Core Web Vitals

Images affect two of the three Core Web Vitals.

CLS: always set dimensions

web.dev lists "images without dimensions" first among the most common causes of poor Cumulative Layout Shift (web.dev: Optimize CLS). Set width and height attributes, or use CSS aspect-ratio, so the browser reserves space before the image loads.

LCP: don't lazy-load the hero

web.dev's lazy-loading guide warns: "Don't lazy-load images that are likely to be in-viewport when the page loads, especially LCP images" (web.dev). WordPress has added loading="lazy" automatically since 5.5, and only to images with both width and height attributes (Make WordPress Core), so check that your theme isn't lazy-loading the featured image at the top of each post.

To be fair about scale: a broken image below the fold is a user-experience bug, not a ranking crisis. I go into what affects rankings and what doesn't in Broken Links and Core Web Vitals: What Actually Affects Rankings.

Step 6: Social preview images

The og:image meta tag sets the image used when a post is shared, and Google lists it among the ways to suggest a preferred image for a page. Google advises choosing an image that is "relevant and representative of the page" and avoiding generic images such as a site logo. When og:image points at a deleted file, every share of that post shows a blank card. Include og:image URLs in your broken-image checks.

Keep it from coming back

  • Before deleting media, check whether it's still used. On WordPress, media plugins or a database search for the filename will tell you.
  • After any migration, CDN change or HTTPS switch, re-crawl images specifically.
  • Avoid hotlinking images you don't control.
  • Monitor continuously. Images break between audits, and the person who notices is usually a reader.

Disclosure: I build Uchik. Uchik crawls a site daily and includes broken images alongside broken links and redirect problems in its emailed report, with the page where each was found. How it works and the features page explain the checks. Limits, also covered in the FAQ: it reads server-delivered HTML, so images injected only by JavaScript may be missed; it doesn't judge alt text quality; and the free plan covers one site. You can sign up for free. Screaming Frog's free version and Search Console are both good options too.

Checklist

  • [ ] Full crawl with image response codes; 4xx images fixed or removed
  • [ ] srcset and <picture> sources checked, not just src
  • [ ] No http:// image URLs on HTTPS pages
  • [ ] Important images use <img> (not CSS backgrounds), with a src fallback
  • [ ] Supported formats; extensions match file types
  • [ ] Image sitemap considered for JavaScript-loaded images
  • [ ] Descriptive alt text on content images; empty alt on decorative ones
  • [ ] Descriptive filenames
  • [ ] width/height or aspect-ratio on every image
  • [ ] Hero/LCP image not lazy-loaded
  • [ ] og:image URLs return 200
  • [ ] Re-crawl after migrations, CDN moves and media clean-ups

Top comments (0)