Skip to content
Back to skills

Web Optimize

ASecurity

Convert images to AVIF and WebP with responsive srcset widths, and judge if it is worth it. Use to optimize, compress or resize images for a web page.

  • 3 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 20, 2026
ai-agentsrustgoshellbashgitapi

Works with

  • api

Security analysis

A100/100

Pro scans all 2 files and shows the line behind each finding

Scanned October 20, 2026

npx -y skills add imagile-dev/claude-plugins --skill web-optimize --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Web Optimize?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Web Optimize
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/imagile-dev-web-optimize/badge)](https://www.skillsdirectory.com/skills/imagile-dev-web-optimize)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: web-optimize
description: Convert images to AVIF and WebP with responsive srcset widths, and judge if it is worth it. Use to optimize, compress or resize images for a web page.
---

Produce AVIF/WebP derivatives at responsive widths, with a legacy fallback and a ready `<picture>` block. Originals are never modified or deleted.

## First: decide whether this is worth doing

Web optimization is not free — it multiplies files, adds a build/deploy step, and complicates the asset story. Run it when the image is actually going to be served over HTTP to real users. Assess from context before asking:

**Do it when** the repo looks like a web project (`package.json`, `index.html`, `wwwroot/`, `public/`, `static/`, Next/Astro/Vite/Blazor), *or* the image is destined for a page — hero, banner, background, og:image, product shot, avatar — *or* the user mentions page weight, load time, LCP, or Lighthouse.

**Skip it when** the image is for print, a slide deck, a PDF, a Figma import, or a doc; when it's a master intended for further editing; when the user just wants to look at it; or when it's already small (under ~50 KB) and the extra files buy nothing.

**Say "use SVG instead" when** the asset is a logo, icon, or flat geometric mark. No raster format competes with vector here at any size, and the script will tell you when it detects flat art.

If it's genuinely unclear *and* the payoff is real (a large image in a repo that might be a web project), ask once with `AskUserQuestion` rather than guessing — but don't interrupt for a 30 KB image or an obvious one-off. Defaulting to "no" and mentioning it's available is better than optimizing things nobody serves.

## Prerequisites

**`uv`** — verify with `uv --version`. Dependencies (`pillow>=11.3`) resolve automatically from the script's inline metadata. AVIF encoding is built into modern Pillow; no `pillow-avif-plugin` needed. If a Pillow build lacks the AVIF encoder the script says so and emits WebP only.

## Usage

```bash
uv run "${CLAUDE_PLUGIN_ROOT}/skills/web-optimize/scripts/optimize_for_web.py" hero.png --widths 480,960,1440 --snippet
```

Derivatives land in a `web/` folder beside the source (or `-o DIR`). Point it at a folder to batch a whole directory tree.

| Flag | Notes |
|------|-------|
| `--widths` | Comma-separated srcset widths. Widths above the source are dropped — it never upscales |
| `--formats` | Default `avif,webp` |
| `--fallback` | `jpeg` (default), `png`, `none`. Auto-switches to PNG when the image has alpha |
| `--art` | `auto` (default), `photo`, `flat` — see below |
| `--lossless` | Force lossless; mainly for screenshots containing text |
| `--avif-quality` / `--webp-quality` / `--jpeg-quality` | Defaults 70 / 80 / 82 |
| `--snippet` | Print a `<picture>` block |
| `--base-url` / `--sizes` / `--alt` | Feed the snippet |

On Git Bash for Windows, a `--base-url` starting with `/` gets rewritten into a Windows path (`/assets/img` becomes `C:/Program Files/Git/assets/...`). Use PowerShell for that flag, or prefix with `//`, or just edit the emitted snippet.

## Photos vs flat art — they behave in opposite directions

This is the part that's easy to get wrong, so the script detects it (`--art auto`) rather than trusting the caller:

- **Photographic / gradient content** — lossy AVIF and WebP both win big, and a width ladder pays off. This is the case the standard advice is written for.
- **Flat art (icons, logos, few colors)** — the advice inverts. Lossy AVIF can land *several times larger* than the source PNG. The script drops AVIF, switches to lossless WebP, and emits a single width, because downscaling antialiases hard edges into gradients and makes the smaller file *bigger*. It also reminds you that SVG is the real answer.

Detection is deliberately conservative (256 distinct colors), so it fires on genuinely flat sources — rasterized SVG exports, UI screenshots — and **not** on `gpt-image-2` output. A generated "flat vector icon" measured 6,425 distinct colors, because the model antialiases and dithers even when the result looks flat. That's the right call: treating it as a photo took a 759 KB PNG down to a 9 KB AVIF with no visible degradation.

### AVIF does not always win

"AVIF is ~50% smaller than JPEG" is a statement about averages, not about your image. On a real generated photo at the default settings, **WebP beat AVIF at every width** (184 KB vs 201 KB at full width). Since `<picture>` serves the first supported `<source>`, a hardcoded AVIF-first list would have shipped the *larger* file to the more capable browser.

So the script orders `<source>` elements by measured output size rather than by reputation, and says so when AVIF loses. Trust the numbers it prints over any format ranking, including this one.

As a backstop, any format whose full-width output is larger than the source is deleted and reported — shipping the original is strictly better than shipping a bloated derivative. If everything gets dropped, that's the tool telling you this image doesn't need optimizing.

Always read the reported percentages instead of assuming a win. Savings depend heavily on the source: converting the PNG that `gpt-image-2` returns is a dramatic reduction, while re-encoding an already-compressed JPEG is a modest one.

## Wiring it into a page

The emitted `<picture>` puts the smallest measured format first, then the other, then the fallback `<img>`; browsers take the first type they support. It ships `width`/`height` (reserves layout, avoids CLS), `loading="lazy"`, and `decoding="async"`.

Two things to adjust by hand:
- **`sizes`** defaults to `100vw`, which is only right for full-bleed images. For a constrained column, something like `(max-width: 768px) 100vw, 1200px` prevents mobile from pulling a desktop-sized file.
- **The LCP image** — usually the hero — should have `loading="lazy"` **removed** and `fetchpriority="high"` added. Lazy-loading the LCP image makes the metric worse, not better.

## Self-healing

If the script misbehaves (a Pillow API change, an encoder that isn't available, output that doesn't match the reported sizes), fix `scripts/optimize_for_web.py` and this SKILL.md directly rather than only working around it for the current run.

Files in this skill

  • SKILL.md6.1 KB
  • scripts/optimize_for_web.py12 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.