# Seo Images

> Image SEO audit for a URL or domain. Pulls raw image inventory via Firecrawl, then audits alt-text quality, modern-format coverage (WebP / AVIF), responsive sizing (`srcset` / `sizes`), lazy-loading and LCP signals (`loading`, `fetchpriority`, `decoding`), CLS-safe dimensions, descriptive file names, and `ImageObject` JSON-LD. Optional PageSpeed Insights cross-reference for real byte-saving estim…

- **Type:** Skill
- **Install:** `agentstack add skill-amirjahfar1-automate-seo-with-claude-seo-images`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [amirjahfar1](https://agentstack.voostack.com/s/amirjahfar1)
- **Installs:** 0
- **Category:** [AI & ML](https://agentstack.voostack.com/c/ai-and-ml)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [amirjahfar1](https://github.com/amirjahfar1)
- **Source:** https://github.com/amirjahfar1/automate-seo-with-claude/tree/main/skills/seo-images
- **Website:** https://nextbrainsolutions.com/

## Install

```sh
agentstack add skill-amirjahfar1-automate-seo-with-claude-seo-images
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# Image SEO Audit

A focused, page-level (or domain-sample) audit of every `` and `` on the target. Surfaces alt-text issues, format gaps (WebP/AVIF coverage), responsive-image gaps (`srcset` / `sizes`), LCP and CLS risk, and missing `ImageObject` markup. Output is a prioritised remediation list plus paste-ready `` and JSON-LD snippets.

> **Adapted from [`AgriciDaniel/claude-seo`](https://github.com/AgriciDaniel/claude-seo)'s `seo-images` skill** (MIT). Rubric, lazy-loader taxonomy, and severity ladder track the upstream implementation; data sources are wired to this catalogue's DataForSEO / Firecrawl / Google APIs stack.

## Prerequisites

- **Required for inventory:** `mcp__firecrawl-mcp__firecrawl_scrape` (raw HTML access). `WebFetch` returns markdown only — every `` attribute (`srcset`, `sizes`, `loading`, `fetchpriority`, `width`, `height`, `data-src*` lazy variants) is stripped before the skill ever sees it. Without Firecrawl the audit cannot run. Install via `bash extensions/firecrawl/install.sh`.
- **DataForSEO MCP server connected** — used for the page set in domain mode (`mcp__dataforseo__dataforseo_labs_google_relevant_pages` for top traffic pages) and for `mcp__dataforseo__on_page_instant_pages` (returns the page's image resource list, cross-checking the Firecrawl inventory).
- **Optional (PSI byte-saving estimates):** `google-api.json` configured (Tier 0 — API key only). When present, step 9 runs and adds real Lighthouse `wastedBytes` per image to the remediation list.
- User provides: a target URL (single-page audit) or a domain (sampled audit). For domains, the skill confirms how many pages to sample before spending Firecrawl credits.

## Process

1. **Validate target & preflight.** Normalise the URL (strip trailing slash, decode IDN). Resolve mode:
   - **URL mode** (default for inputs that look like a single page): the target is one URL. Cost: 1 Firecrawl credit + optional PSI calls.
   - **Domain mode** (input is a bare domain or the user explicitly asks for a domain-wide audit): map first, then scrape a sample.
   - **Preflight checks** (mirror `skills/seo-firecrawl/references/preflight.md` where it applies):
     - Confirm Firecrawl is connected. If not, abort with the install command and stop.
     - If Google APIs are wired up (`~/.config/seo-skills/google-api.json` present), record the detected tier; step 9 will use it. If not, mark step 9 as skipped.

2. **Gather image inventory** `mcp__firecrawl-mcp__firecrawl_scrape` (URL mode) or `firecrawl_map` + `firecrawl_scrape` (domain mode)
   - **URL mode:** scrape the target with `formats: ["html", "markdown"]` and `onlyMainContent: false` (we want nav/footer images too — hero logo, footer trust badges, decorative imagery all matter for the audit). For SPAs, pass `waitFor: 2000` so lazy-injected images appear in the rendered DOM. Parse every `` and every `` from the returned `html`. Capture per image:
     - `src`, `srcset`, `sizes`, `alt`, `loading`, `fetchpriority`, `decoding`, `width`, `height`, `role`, `aria-hidden`
     - Lazy-loader attributes: `data-src`, `data-srcset`, `data-lazy-src`, `data-original`, `data-perfmatters-src`, `data-perfmatters-srcset`, `data-ewww-src`, `data-eio`
     - Class signals: `lazyload`, `lazyloaded`, `lazy`, `perfmatters-lazy`, `lazyload-eio`
     - Parent `` `` entries: `type`, `srcset`, `media`
     - Resolved absolute URL (for cross-origin / CDN detection)
   - **Domain mode:** run `firecrawl_map` (default `limit: 500`, hard cap; cost: ~0.5 credit per discovered URL — surface the estimate before running). From the URL list, select a sample of up to 10 pages: homepage, plus the top traffic landing pages (from `mcp__dataforseo__dataforseo_labs_google_relevant_pages` if DataForSEO is connected, otherwise the deepest-nested URLs found in the sitemap — these are usually the content pages, not category indexes). Confirm the sample list and credit cost before scraping. Then scrape each (1 credit per page). Inventory is the union of every image on the sampled pages.
   - **Cross-check with DataForSEO On-Page:** run `mcp__dataforseo__on_page_instant_pages` on each scraped URL — its returned resource list enumerates the page's image resources (URLs, sizes, status codes). Reconcile against the Firecrawl-parsed inventory: any image resource On-Page reports that Firecrawl missed (or that returns a 4xx/5xx) is added to the inventory and flagged `image_broken` (High) when its status is an error code.
   - **CSS background-images:** flag as a known blind spot. We don't audit `background-image: url(...)` in stylesheets — those are not crawlable as content images by Google and don't get image-search visibility. Surface "{n} likely background-images detected (computed style references) — out of scope for this audit; review separately if hero/feature images are CSS-based" in the synthesis.

3. **Alt-text audit**
   - Load `references/image-checks.md` § Alt text. For each image:
     - **Presence:** missing `alt` (not `alt=""` — the empty-string form is valid for purely decorative images). Severity High.
     - **Decorative-but-not-marked:** `alt=""` is fine only if the image is genuinely decorative. Flag images with `alt=""` that also have a non-decorative `src` (e.g. product photo path, hero image path) as "verify decorative intent" (Medium).
     - **Generic text:** `alt` value matches a generic pattern — bare filename (`image.jpg`, `IMG_1234.png`), single generic noun (`photo`, `picture`, `image`, `banner`), CTA copy (`click here`, `read more`, `learn more`). Severity High.
     - **Length:** `alt` outside the 10–125 character window. Below 10 → Medium (probably not descriptive). Above 125 → Low (likely too verbose; screen readers truncate around there).
     - **Keyword stuffing:** the same keyword token appears 3+ times in the alt, or the alt is >50% keyword tokens. Severity Medium.
     - **Identical alt across multiple images on the page:** flag as a templating bug (Medium) — every product photo on a PDP should not share the same alt.

4. **Format coverage (WebP / AVIF)**
   - For each image, classify its served format from `src` extension (`.webp`, `.avif`, `.jpg`/`.jpeg`, `.png`, `.gif`, `.svg`) and `` `` `type` attributes (`image/avif`, `image/webp`).
   - Compute three coverage metrics for the page (or domain sample):
     - **% images served as WebP or AVIF directly** (via the chosen `` or chosen `` ``).
     - **% images wrapped in `` with at least one modern-format ``** (progressive enhancement — fallback chain).
     - **% images stuck on legacy formats** (JPEG / PNG / GIF) with no modern alternative.
   - Per-image flags:
     - Legacy format with no `` modern alternative → `image_legacy_format` (Medium).
     - Animated GIF over 500 KB → recommend video (``) instead (Medium — performance + LCP impact). Source: Google PSI `efficient-animated-content` audit.
     - SVG used for photographic content → `image_svg_misuse` (Low — file size will be enormous; SVG is for icons/illustrations).
   - **JPEG XL note.** Chromium announced restoration of JPEG XL decoding (Rust-based) in November 2025 but it's not yet in Chrome stable. Surface as a Tips note: not actionable today, monitor for 2026.

5. **Responsive coverage (`srcset` / `sizes`)**
   - For each non-SVG raster image:
     - Missing `srcset` → `image_no_srcset` (Medium). Browser cannot pick a size-appropriate file; mobile users download desktop-sized images.
     - `srcset` present but no `sizes` and not inside `` → `image_no_sizes` (Medium). Browser falls back to viewport width assumptions and can pick the wrong candidate.
     - `srcset` declared but all candidates are the same width descriptor (`1x` only, or all `w` values within 100 px of each other) → `image_srcset_useless` (Low).

6. **Lazy loading & LCP signals**
   - For each image, classify the lazy-loading mechanism using `references/lazy-loaders.md`'s taxonomy: `native` / `perfmatters` / `ewww` / `js-generic` / `none`. Report `lazy_method` alongside `loading` so a JS-loader-driven page isn't mis-flagged for missing `loading="lazy"` (the native attribute is intentionally absent there — the loader handles it).
   - **LCP-candidate heuristic.** The LCP image is typically the first `` that:
     - Appears above the fold on a typical mobile viewport (no exact viewport without rendering; heuristic = first `` in the rendered DOM that is not inside a `` / `` / `` and has no `loading="lazy"` ancestor),
     - Has a large rendered area (width × height attributes both ≥ 300, or `` `` with viewport-spanning `sizes`).
   - For the LCP candidate:
     - `loading="lazy"` set → `image_lcp_lazy` (High). Lazy-loading the LCP image directly harms LCP.
     - No `fetchpriority="high"` → `image_lcp_no_fetchpriority` (Medium). Lighthouse's `prioritize-lcp-image` audit; setting `fetchpriority="high"` moves the LCP image to the front of the browser's network queue.
   - For below-fold images (not the LCP candidate, not inside the first viewport):
     - Neither native `loading="lazy"` nor any JS-loader signal → `image_below_fold_eager` (Medium). Below-fold images should defer.
     - Missing `decoding="async"` → `image_no_decoding_async` (Low). Async decode prevents image decoding from blocking the main thread for non-LCP images.

7. **CLS dimensions**
   - For each image:
     - Missing both `width` and `height` attributes AND no inline `aspect-ratio` style → `image_unsized` (High). The browser cannot reserve space; the image will shift content when it loads. Matches Lighthouse `unsized-images`.
     - `width` and `height` present but the ratio mismatches the actual displayed ratio by >5% → `image_aspect_mismatch` (Low). Layout will shift on load.
   - The fix for both is the same: set `width` and `height` attributes to the image's intrinsic dimensions, and let CSS handle responsive scaling.

8. **File-name quality**
   - For each image's resolved URL, extract the filename. Flag:
     - Camera-default names (`IMG_xxxx`, `DSC_xxxx`, `DSCN_xxxx`, `P_xxxx`, `Photo_xx`) → `image_camera_filename` (Low).
     - Random-hash names (`a3f9b2c.jpg`, `0e8d1f7.webp` — hex/base64 patterns with no human-readable tokens) → `image_hash_filename` (Low). Common with image CDNs; verify there's no SEO-friendly version available.
     - All-uppercase or all-underscore filenames → `image_filename_style` (Low). Convention is lowercase + hyphens.
   - Don't flag every CDN-served image as a problem — many CMSes hash filenames for cache busting and that's fine. The signal is meaningful when paired with a missing or generic `alt` on the same image (the page has no signal at all about what the image depicts).

9. **`ImageObject` JSON-LD: detect, validate, generate**
   - **Detect:** parse every `` block returned by Firecrawl. Find existing `ImageObject` blocks — either top-level (for image-search rich results) or nested under `Article.image`, `Product.image`, `Recipe.image`, etc.
   - **Validate** against Google Images' guidelines (see `references/image-checks.md` § ImageObject for the field list). For a top-level `ImageObject`:
     - Required: `@context`, `@type: ImageObject`, `contentUrl` (the image URL), `creator` or `copyrightHolder`.
     - Recommended for licensable-images rich results: `license` (URL to the license terms), `acquireLicensePage` (URL where users can buy/license the image), `creditText` (how the creator should be credited).
     - Common mistakes: `url` instead of `contentUrl`, `author` as a bare string instead of a `Person` / `Organization` object, dimensions as strings instead of `Number`.
   - **Generate:** for each image that doesn't already have an `ImageObject` block AND that meets the "worth marking up" threshold (the image is the page's hero / first-fold and the page has a clear creator/owner), produce a paste-ready block from `templates/image-object.json`, filling in fields from the live HTML. Mark unresolved fields as `{REPLACE: ...}`. The generated file is emitted as `02-remediation/image-object.jsonld` (the `.jsonld` extension marks it as a deliverable for ``).
   - **Don't generate `ImageObject` for every ``.** It's noise. Limit to the hero image and any image that should be eligible for licensable-images rich results.

10. **Optional: PageSpeed Insights byte savings** *(only if `~/.config/seo-skills/google-api.json` is present, Tier ≥ 0)*
    - Run `python3 scripts/pagespeed_check.py "{url}" --strategy=mobile --json` and `--strategy=desktop --json` (2 API calls per target URL — within PSI's 25k/day free quota).
    - Pull the following audits from the JSON response and merge per-image `wastedBytes` into the remediation list:
      - `modern-image-formats` — bytes savable by serving WebP/AVIF (overlaps with step 4; PSI's number is authoritative).
      - `uses-optimized-images` — bytes savable by re-compressing.
      - `uses-responsive-images` — bytes savable by serving size-appropriate files (overlaps with step 5).
      - `offscreen-images` — bytes deferrable by lazy-loading below-fold images (overlaps with step 6).
      - `unsized-images` — page elements missing dimensions (cross-checks step 7).
      - `prioritize-lcp-image` — confirms or contradicts the step-6 LCP-candidate heuristic and gives PSI's authoritative LCP element.
      - `efficient-animated-content` — confirms animated-GIF flagging from step 4.
    - Each PSI audit returns `details.items[]` with `url` and `wastedBytes`. Join on image URL (resolved absolute) and tag each remediation row with `psi_wasted_bytes` so the prioritised list orders by real savings, not heuristic severity alone.
    - **If PSI is configured but returns no audits** (likely a 4xx — usually a private/protected URL Lighthouse can't load): note "PSI: could not analyse {url} ({reason})" and continue with non-PSI signals.

11. **Synthesise** `IMAGES.md`. Build the remediation table sorted by:
    1. Severity (Critical → High → Medium → Low),
    2. Within severity: PSI `wastedBytes` descending (when PSI ran), else affected-image count descending,
    3. Then alphabetical by issue code.

## Output format

Create a folder `seo-images-{target-slug}-{YYYYMMDD}/` with:

```
seo-images-{target-slug}-{YYYYMMDD}/
├── IMAGES.md                       (synthesised audit + remediation list — primary deliverable)
├── images.csv                      (every image with all audit columns — engineering pastes into Jira)
├── 01-inventory.md                 (per-page image list with raw attributes)
├── 02-remediation/
│   ├── picture-snippets.md         (paste-ready  blocks for the top N legacy-format images)
│   ├── alt-text-rewrites.md        (suggested alts for missing / generic-text cases)
│   └── image-object.jsonld         (generated ImageObject for the hero image, if applicable)
└── 03-psi-report.md                (PSI image-audit breakdown — only if Google APIs configured)
```

`IMAGES.md` follows this shape:

```markdown
# Image SEO Audit: {URL or domain}

> Snapshot dated {YYYY-MM-DD} · Mode: {URL | domain-sample (n pages)} · Images analysed: {n}

## Coverage at a glance

| Metric | Result |
|---|---|
| Total images | {n} |
| Missing alt text | {n} ({pct}%) |
| Generic / templated alt text | {n} ({pct}%) |
| Modern format (WebP/AVIF) coverage | {pct}% direct, {pct}% via `` fallback |
| `srcset` present (responsive) | {pct}% |
| `loading` strategy detected | native: {pct}% · JS-loader: {pct}% · none: {pct}% |
| LCP image flagged | {yes/no — element + risk} |
| Unsized (CLS risk) | {n} ({pct}%) |
| `ImageObject` JSON-LD | {present / partial / missing} |

## Top 10 remediations (severity × byte savings)

| Rank | Issue code | Severity | Images | PSI wastedBytes | Fix | Effort |
|---|---|---|---|---|---|---|
| 1 | image_lcp_lazy | High | 1 | 480 KB | Remove `loading="lazy"`; add `fetchpriority="high"` | S |
| 2 | image_legacy_format | Medium | 14 | 2.1 MB | Convert to WebP, wrap in `` with fallback | M |
| ... |

## By category

### Alt text ({n} issues)
- {n} images missin

…

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [amirjahfar1](https://github.com/amirjahfar1)
- **Source:** [amirjahfar1/automate-seo-with-claude](https://github.com/amirjahfar1/automate-seo-with-claude)
- **License:** MIT
- **Homepage:** https://nextbrainsolutions.com/

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-amirjahfar1-automate-seo-with-claude-seo-images
- Seller: https://agentstack.voostack.com/s/amirjahfar1
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
