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 estimates. Produces a prioritised remediation list plus paste-ready picture-element markup and `ImageObject` schema. Distinct from `seo-technical-audit` (which surfaces audit-flagged image issues at the site level) and from `seo-schema` (which generates page-level JSON-LD but not image-specific markup). Use when the user asks for "image SEO", "image audit", "alt-text audit", "WebP coverage", "AVIF", "responsive images", "lazy loading", "CLS images", "image schema", "ImageObject", "image rich results", "licensable images", or "optimise images".
npx skills add https://github.com/seranking/seo-skills --skill seo-images
A focused, page-level (or domain-sample) audit of every <img> and <picture> 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 <picture> and JSON-LD snippets.
> Adapted from 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 SE Ranking / Firecrawl / Google APIs stack.
mcp__firecrawl-mcp__firecrawl_scrape (raw HTML access). WebFetch returns markdown only — every <img> 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.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.skills/seo-firecrawl/references/preflight.md where it applies):~/.config/seo-skills/google-api.json present), record the detected tier; step 9 will use it. If not, mark step 9 as skipped.mcp__firecrawl-mcp__firecrawl_scrape (URL mode) or firecrawl_map + firecrawl_scrape (domain mode)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 <img> and every <picture> from the returned html. Capture per image:src, srcset, sizes, alt, loading, fetchpriority, decoding, width, height, role, aria-hiddendata-src, data-srcset, data-lazy-src, data-original, data-perfmatters-src, data-perfmatters-srcset, data-ewww-src, data-eiolazyload, lazyloaded, lazy, perfmatters-lazy, lazyload-eio<picture> <source> entries: type, srcset, mediafirecrawl_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 DATA_getDomainKeywords's page aggregation if SE Ranking 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.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.references/image-checks.md § Alt text. For each image:alt (not alt="" — the empty-string form is valid for purely decorative images). Severity High.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).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.alt outside the 10–125 character window. Below 10 → Medium (probably not descriptive). Above 125 → Low (likely too verbose; screen readers truncate around there).src extension (.webp, .avif, .jpg/.jpeg, .png, .gif, .svg) and <picture> <source> type attributes (image/avif, image/webp).<img src> or chosen <picture> <source>).<picture> with at least one modern-format <source> (progressive enhancement — fallback chain).<picture> modern alternative → image_legacy_format (Medium).<video autoplay muted loop playsinline>) instead (Medium — performance + LCP impact). Source: Google PSI efficient-animated-content audit.image_svg_misuse (Low — file size will be enormous; SVG is for icons/illustrations).srcset / sizes)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 <picture> → 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).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).<img> that:<img> in the rendered DOM that is not inside a <header> / <nav> / <aside> and has no loading="lazy" ancestor),<picture> <source> with viewport-spanning sizes).loading="lazy" set → image_lcp_lazy (High). Lazy-loading the LCP image directly harms LCP.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.loading="lazy" nor any JS-loader signal → image_below_fold_eager (Medium). Below-fold images should defer.decoding="async" → image_no_decoding_async (Low). Async decode prevents image decoding from blocking the main thread for non-LCP images.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.width and height attributes to the image's intrinsic dimensions, and let CSS handle responsive scaling.IMG_xxxx, DSC_xxxx, DSCN_xxxx, P_xxxx, Photo_xx) → image_camera_filename (Low).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.image_filename_style (Low). Convention is lowercase + hyphens.alt on the same image (the page has no signal at all about what the image depicts).ImageObject JSON-LD: detect, validate, generate<script type="application/ld+json"> 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.references/image-checks.md § ImageObject for the field list). For a top-level ImageObject:@context, @type: ImageObject, contentUrl (the image URL), creator or copyrightHolder.license (URL to the license terms), acquireLicensePage (URL where users can buy/license the image), creditText (how the creator should be credited).url instead of contentUrl, author as a bare string instead of a Person / Organization object, dimensions as strings instead of Number.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 <script type="application/ld+json">).ImageObject for every <img>. 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)*
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).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.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.11. Optional: SE Ranking audit cross-reference *(only if SE Ranking MCP is connected and a recent audit exists)*
DATA_listAudits → find the most recent audit for the domain. If none exists or it's >30 days old, skip this step (don't trigger a new audit from the image skill — that's seo-technical-audit's call to make).DATA_getAuditPagesByIssue:images_oversized (or whatever SE Ranking's current code is for "uncompressed images")images_no_altimages_broken (404 / 5xx image URLs)images_no_dimensions (CLS)12. Synthesise IMAGES.md. Build the remediation table sorted by:
wastedBytes descending (when PSI ran), else affected-image count descending,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 <picture> 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)
└── 04-audit-cross-ref.md (image-related SE Ranking audit issues — only if step 11 ran)
IMAGES.md follows this shape:
# 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 `<picture>` 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 `<picture>` with fallback | M |
| ... |
## By category
### Alt text ({n} issues)
- {n} images missing `alt` entirely. See `02-remediation/alt-text-rewrites.md` for suggested rewrites.
- {n} images with generic alt (`image.jpg`, `photo`, "click here").
- {n} images with identical alt across multiple images (templating bug).
### Format coverage ({pct}% modern)
- {n} images stuck on legacy JPEG/PNG. See `02-remediation/picture-snippets.md`.
- {n} animated GIFs >500 KB — recommend video.
### Responsive sizing ({pct}% have `srcset`)
- {n} images without `srcset`.
- {n} images with `srcset` but no `sizes`.
### Lazy loading & LCP
- LCP candidate: `{img src or selector}` — {risk summary}.
- {n} below-fold images loading eagerly.
- {n} images missing `decoding="async"`.
### CLS dimensions ({n} unsized)
- {n} images without `width`/`height` attributes.
- {n} images with aspect-ratio mismatches.
### File names ({n} flagged)
- {n} camera-default names (IMG_xxxx).
- {n} hash-only filenames coupled with a missing/generic alt.
### ImageObject JSON-LD
- Currently present: {none | block-level on hero | partial}.
- Recommended additions: {none | hero-image ImageObject for licensable-images rich result}.
## Paste-ready remediations
See `02-remediation/`:
- `picture-snippets.md` — `<picture>` blocks for the top N legacy-format images.
- `alt-text-rewrites.md` — alt-text rewrites for missing / generic cases.
- `image-object.jsonld` — `ImageObject` block for the hero image.
## Out of scope for this skill
- **File-level optimisation** (running `cwebp` / `exiftool` / ImageMagick / `ffmpeg` against the actual binary). This skill audits markup and references; converting and re-uploading the files is engineering work — see the pipeline note in `references/image-checks.md` § Optimisation pipeline if you want a starting recipe.
- **CSS background-images.** {n} likely background-image references detected via computed style, but not audited. They don't appear in Google Images and aren't subject to the `<img>`-tag rubric.
- **Site-wide audit at >10 pages.** This is a sampled audit. For domain-level "every image on every page", run `seo-technical-audit` first to surface the audit-grade signals, then come back here for sample-level deep audit.
## Recommended next steps
- {`seo-technical-audit` if domain-wide image issues need to be quantified — uncompressed-images counts, etc.}
- {`seo-schema` if `ImageObject` was generated and the page also needs `Article` / `Product` / etc. markup.}
- {`seo-google pagespeed` for the full Lighthouse breakdown (this skill only pulls image-specific audits).}
images.csv columns: page_url,image_url,alt,alt_length,alt_issue,format,in_picture,modern_source,srcset,sizes,loading,lazy_method,fetchpriority,decoding,width,height,unsized,lcp_candidate,filename_issue,psi_wasted_bytes,severity,fix,effort.
<picture> is the right answer. When recommending modern formats, always recommend the <picture> element with AVIF + WebP <source> and a JPEG fallback <img> — not raw <img src=".avif">. AVIF is at 93%+ support and WebP at 97%+, but the fallback is what makes the markup safe for older clients and crawlers.loading="lazy" defaults that apply to the hero. The skill's LCP heuristic catches the most likely culprit; PSI (step 10) confirms it authoritatively.alt="" is correct for purely decorative images (a hairline-rule SVG, a pure background-spacer image). It tells screen readers to skip the image. Don't auto-flag every empty alt — flag only those where the image filename and context suggest the image carries content.IMAGES.md's "By category" section, not just the aggregate counts. Aggregate-only output is useful when there are 100+ images; below that it hides the specifics.seo-firecrawl — when the user already scraped a page and now wants the image-specific cut.seo-technical-audit — when a site-wide audit flagged image issues and the user wants the deep per-image rubric.seo-page — when a URL-level keyword/traffic verdict is "refresh" and images are part of the refresh.seo-schema — when the page also needs Article / Product / LocalBusiness schema beyond ImageObject.seo-google pagespeed — for the full Lighthouse report (this skill cherry-picks the image audits; PSI has 100+ more).seo-drift — to baseline image markup and detect regressions after a CMS or theme upgrade.Universal AI-powered web scraper for any platform. Scrape data from Instagram, Facebook, TikTok, YouTube, LinkedIn, X/Twitter, Google Maps, Google Search, Google Trends, Reddit, Airbnb, Yelp, and 15+ more platforms. Use for lead generation, brand monitoring, competitor analysis, influencer discovery, trend research, content analytics, audience analysis, review analysis, SEO intelligence, recruitment, or any data extraction task.
eBay sold-listings scraper across 8 marketplaces (ebay.com/.co.uk/.de/.fr/.it/.es/.ca/.com.au). Takes keyword plus filters (category, price range, item condition, item location, sort order, completed toggle) and returns paginated real-sale records with itemId, url, title, condition, conditionId, endedAt, soldPrice, soldCurrency, listingType (best_offer_accepted / buy_it_now / auction), isBestOfferAccepted, buyingFormat, bidCount, shipping, totalPrice, thumbnails, seller info, sellerType. Use when user mentions ebay sold, ebay sold listings, ebay sold prices, ebay completed listings, ebay comps, ebay resale prices, ebay auction sold results, ebay best offer accepted, scrape ebay sold, ebay market research, ebay pricing intelligence, ebay flipping research, real sold prices ebay, get sold prices from ebay. Also applies to price benchmarking, sold-price analytics, resale valuation, cross-marketplace price arbitrage, appraisal for collectibles, brand demand tracking on ebay.
Etsy keyword search scraper: given a search keyword and optional page number, returns paginated product listings with listingId, shopId, title, url, image, salePrice, originalPrice, currency, rating, reviewCount, shopName, isAd, freeShipping, badge from etsy.com search results. Use when user mentions Etsy, etsy.com, Etsy search, search Etsy by keyword, scrape Etsy listings, extract Etsy products, Etsy product search, Etsy marketplace search, find products on Etsy, Etsy handmade search, Etsy vintage search, Etsy craft search, bulk Etsy product export, Etsy price monitoring, Etsy competitor research, Etsy top listings, Etsy bestseller extraction, Etsy sales data, Etsy shop discovery via keyword. Also applies to trend research on handmade or craft niches, competitor keyword ranking on Etsy, sourcing Etsy suppliers by product type, and any paginated bulk product collection driven by a search keyword.
Etsy product detail scraper: given an Etsy listing URL, returns full product detail including listingId, title, priceCurrent, priceOriginal, currency, images (all), description, shopName, shopUrl, rating, reviewCount, favorites, inCartCount, variations (with per-option price ranges), highlights, listedDate, relatedTags. Use when user mentions Etsy product, Etsy listing detail, Etsy item info, Etsy product page, scrape Etsy listing, extract Etsy product data, Etsy price and variations, Etsy product images, Etsy product description, Etsy shop from listing, Etsy favorites count, Etsy sale count, Etsy variations extraction, Etsy listing metadata, single Etsy product scrape, bulk enrich Etsy listing URLs, Etsy product details export. Also applies to competitor product monitoring, price and variation tracking on a specific listing, favorites/wishlist popularity tracking, description mining for SEO analysis, and any per-listing enrichment task.
Scrapes second-hand item search results from Goofish (闲鱼/xianyu, goofish.com) — China's largest second-hand marketplace. Input: keyword, optional sort/filter params. Output: list of items with id, title, price, image, location, want-count per page (30 items/page). Use when user mentions goofish, 闲鱼, xianyu, 二手交易, second-hand marketplace China, 二手商品搜索, search used goods, scrape goofish listings, xianyu search results, collect second-hand prices, monitor used item prices, 闲鱼关键词搜索, 闲鱼数据采集, 批量抓取闲鱼, goofish scraper, goofish data, xianyu data extraction, 二手商品价格监控, used iPhone prices, 二手手机价格. Also applies to: price research on Chinese second-hand market, competitor product monitoring via used goods listings, inventory analysis.
This skill helps users automatically scrape business data from Google Maps using the BrowserAct Google Maps API. Agent should proactively trigger this skill for needs like finding restaurants in a specific city, extracting contact info of dental clinics, researching local competitors, collecting addresses of coffee shops, generating lead lists for specific industries, monitoring business ratings and reviews, getting opening hours of local services, finding specialized stores (e.g., Turkish-style restaurants), analyzing business categories in a region, extracting website links from local businesses, gathering phone numbers for sales outreach, mapping out service providers in a specific country.
Extracts Google Search results page (SERP) data including organic results, paid ads, related searches, People Also Ask questions, AI Overview text, and total result count from google.com. Use when user mentions Google search results, SERP scraping, google search data, search engine results page, organic rankings, keyword SERP, Google SERP extraction, scrape Google search, Google search API alternative, SEO ranking data, paid search ads, PPC ads on Google, Google search monitoring, keyword research, search results export, check Google rankings, what shows up on Google, search engine scraper, google results checker.
Scrape job listings from Indeed.com by keyword, location, and country. Returns job title, company, salary, rating, description, benefits, and apply links. Use when user mentions Indeed, Indeed scraper, Indeed jobs, scrape Indeed, job search Indeed, Indeed job listings, extract Indeed data, Indeed job data, get jobs from Indeed, Indeed employment data, job market research Indeed, Indeed salary data, bulk job extraction Indeed, Indeed job scraper, monitor Indeed listings, Indeed hiring data, job postings Indeed, Indeed career search. Also applies to: job market analysis, salary benchmarking from Indeed, competitor hiring monitoring, recruitment data collection, building job databases from Indeed search results.
Take seranking/seo-images from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.