Web Assets Optimization
Optimize the assets the app actually ships: images, video, animated media, fonts, and SVG. Prefer safe, measurable wins: smaller files, modern formats, responsive markup, stable layout, deliberate loading order, and framework-compatible delivery.
Leading words for this skill:
- pipeline — extend the project's existing asset tooling; do not replace a working optimizer.
- clean — write final asset paths only; leave no temp folders, unused variants, orphaned heavy sources, or ffmpeg two-pass logs.
- strategy — every asset gets a deliberate decision from the matrix in Step 3 (importance x placement x loading context); no blanket policies.
- LCP — treat LCP candidates (hero image, video poster, CSS background) as highest priority; never lazy-load them.
When not to use
- The user wants generated artwork from prompts → use an image-generation skill.
- The ask is a visual redesign with no asset-performance work.
- Assets are already transformed by a CMS/CDN pipeline, unless the task is markup or config around that CDN.
- Video editing/production work (cuts, color grading, subtitles) — this skill only compresses and delivers.
- Sources are proprietary, or destructive edits are requested without backups.
Hard rules
- Stay clean: optimized files land in kept public/assets paths; remove old unoptimized sources once unreferenced; delete unused variants, experimental output dirs, and ffmpeg
-passlogfile logs before finishing.
- Optimize referenced assets first; unused archives only if the user asks (or delete them when clearly safe).
- Detect the framework before patching markup; prefer framework-native primitives already in use.
- Do not add a package manager or root tooling to documentation-only repositories; in target apps, prefer existing package managers and scripts.
- Never lazy-load an LCP candidate — including video posters and CSS background LCP images.
- Always reserve space:
width/height (or CSS aspect-ratio) on every <img> AND <video>.
- Video: never ship AV1-only; always keep an MP4/H.264 fallback
<source>; H.264 requires -pix_fmt yuv420p, even dimensions, and -movflags +faststart.
- Animated GIFs used in pages: replace with
<video> or animated WebP/AVIF unless the context only accepts image formats (README/Markdown renderers, email, Open Graph).
- Fonts: ship WOFF2 only; font preloads always carry
crossorigin, even same-origin.
- Keep SVG logos/icons as vectors; when running SVGO, keep the
viewBox (removeViewBox: false).
- Preserve semantics: meaningful
alt for content images, alt="" only when decorative; aria-label or nearby text for meaningful <video> (no alt exists).
Branches
| Ask |
Path |
| Audit only |
Steps 1–3, then report (skip generation and markup). |
| Optimize + markup |
Full workflow below. |
| Single asset type (e.g. "compress this video", "fix our fonts", "replace these GIFs") |
Steps 1–3 scoped to that type, then the matching reference file and Steps 5–9 for those assets. |
| CDN/CMS already transforms |
Step 1 + markup/config only; see frameworks.md CMS section. |
| Delivery/LCP tuning only (no re-encoding) |
Steps 1–3 + Step 6 with delivery-strategy.md. |
Workflow
Track with this checklist (one item per step below):
- [ ] 1. Detect framework and asset pipeline
- [ ] 2. Inventory referenced assets (images, video, GIFs, fonts, SVG)
- [ ] 3. Build the per-asset strategy matrix
- [ ] 4. Plan per-type transforms and commands
- [ ] 5. Generate optimized assets into final paths
- [ ] 6. Replace references with optimized markup and loading attributes
- [ ] 7. Delete old unoptimized assets and unused variants
- [ ] 8. Re-scan and verify references stay clean
- [ ] 9. Verify visuals/build and report
Scripts
Resolve this skill's directory (the folder that contains this SKILL.md), then run helpers relative to that root. Pass --root as the target app path.
python3 <skill-dir>/scripts/scan-assets.py --root <app-root> --format markdown
python3 <skill-dir>/scripts/scan-assets.py --root <app-root> --format json > asset-scan.json
python3 <skill-dir>/scripts/generate-sharp-plan.py asset-scan.json --widths 320,640,1024,1536
python3 <skill-dir>/scripts/generate-ffmpeg-plan.py asset-scan.json
Inside this repository only, <skill-dir> is skills/web/web-assets-optimization.
Step 1 — Detect framework and pipeline
Check package.json, framework configs, image/font component imports (next/image, next/font, astro:assets, @nuxt/image, @nuxt/fonts, @sveltejs/enhanced-img), and CDN/CMS loaders. If a working pipeline exists, extend it.
Done when: the framework (or plain HTML) plus any existing optimizers for images AND fonts are named, and the patch strategy is chosen (native component vs plain markup).
See frameworks.md.
Step 2 — Inventory referenced assets
Run scan-assets.py. Review per type: path, bytes, dimensions (video dims are not detected — note as unknown), references, and usage hints (logo, app-icon, favicon, animated, poster, preload, legacy-font-format).
Done when: every referenced local image/video/GIF/font/SVG under --root is listed; large unreferenced assets are noted; remote URLs are listed separately.
Step 3 — Build the strategy matrix
This is the intelligence step — do not skip it. For each asset that matters (raster > 200 KB, any video, any GIF > 100 KB, every shipped font file, SVG > 20 KB, plus every LCP candidate regardless of size), record one strategy row: asset | type | role | placement | action | loading policy.
- Role: LCP candidate / contentful / decorative / functional (icon, favicon).
- Placement: above fold / below fold / offscreen-conditional (modal, carousel, tab) / global (font, sprite).
- LCP candidates include:
<img>, <video> poster, CSS background-image via url(), <image> in <svg>.
Loading policy per role x placement:
| Role x placement |
Loading policy |
| LCP candidate |
eager, fetchpriority="high", never lazy; must be discoverable in initial HTML; preload only if late-discovered (CSS background, font) |
| Above-fold, non-LCP |
eager, default priority, dimensions set |
| Below-fold image/iframe |
loading="lazy", decoding="async", dimensions mandatory |
| Offscreen-conditional (modal, carousel, tab) |
loading="lazy" (or defer the fetch until the trigger interaction for modal/tab content); fetchpriority="low" on prefetched carousel slides so they do not compete with critical resources |
| Below-fold autoplay video loop |
preload="none" + poster, or IntersectionObserver src-swap; pause offscreen |
| Click-to-play video |
preload="metadata" (or none) + poster |
| Long offscreen sections |
content-visibility: auto + contain-intrinsic-size |
| Critical text font |
WOFF2 subset, preload with crossorigin, font-display: swap/fallback + metric-adjusted fallback |
| Secondary/decorative font |
font-display: optional, no preload |
| Animated GIF in page |
replace per animated-media.md, reduced-motion fallback |
Order of work: LCP candidate → oversized above-fold assets → video/GIF byte hogs → fonts → below-fold rasters → SEO/social images. Details and rationale: delivery-strategy.md.
Done when: every qualifying asset has a matrix row and the LCP candidate is identified (or its absence stated).
Step 4 — Plan per-type transforms
Dispatch per type; read the reference file before planning that type:
- Raster images → sharp-cli.md +
generate-sharp-plan.py. Default format policy unless the project constrains otherwise: photos AVIF + WebP + JPEG fallback; UI screenshots WebP + PNG when lossless detail matters; transparent rasters WebP/AVIF with PNG fallback; logos/icons stay SVG; Open Graph/social images JPEG/PNG at platform dimensions.
- Video → video.md +
generate-ffmpeg-plan.py (MP4 + WebM + poster).
- GIF/animated → animated-media.md +
generate-ffmpeg-plan.py.
- Fonts → fonts.md (WOFF2 conversion, subsetting, display strategy).
- SVG → svg.md (SVGO plan, delivery method).
Done when: each matrix row has planned commands or a stated no-op reason.
Step 5 — Generate into final paths
Review the generated command plans, then run them. Target final kept paths (avoid --out-dir unless you will move winners into final paths and delete the temp dir before finishing). Produce only variants the app will reference; delete ffmpeg pass logs.
Done when: optimized files exist at final paths for every planned row and no temp output or log remains.
Step 6 — Replace references
Apply the matrix's loading policy while patching markup. Before editing image markup, load responsive-images.md and apply every Core rule; for video/GIF markup follow video.md and animated-media.md; for @font-face/preload edits follow fonts.md; for priorities/preload/caching follow delivery-strategy.md.
Also update manifests, favicons, Open Graph, CSS url(...), MDX/Markdown, and structured data when they point at old files. Prefer framework-native patterns when the pipeline uses them.
Done when: no runtime reference still points at an unoptimized original that has a replacement, and every touched element carries the loading attributes its matrix row prescribes (or exceptions are documented).
Step 7 — Delete old assets
Remove unreferenced heavy originals (rasters, videos, GIFs, legacy font formats), unused generated variants, and any temp output directory. Keep SVG sources and any source files the user asked to retain.
Done when: the tree is clean — only referenced optimized assets, intentional vectors, and documented kept sources remain.
Step 8 — Re-scan and verify cleanliness
Rerun scan-assets.py. Confirm optimized references are in place, no orphaned heavy assets remain, and no legacy font formats are still shipped.
Done when: the rescan matches the clean bar above.
Step 9 — Verify and report
Check desktop/mobile render, sensible srcset picks, video plays with correct source order and poster, autoplay loops are muted + playsinline, fonts render without layout jumps, no CLS from missing dimensions, LCP not lazy-loaded, and the build passes with the project's existing commands. Compare bytes before/after per type.
Done when: validation notes are recorded and the report below is filled.
Output format
## Web assets optimization summary
- Framework/pipeline detected: <framework and asset tooling>
- Assets scanned: <count> (<images/videos/GIFs/fonts/SVG counts>); bytes before → after: <totals>
- LCP candidate: <asset and treatment>
- Highest-priority fixes: <short list>
## Strategy matrix
| Asset | Type | Role | Placement | Action | Loading policy |
| --- | --- | --- | --- | --- | --- |
| ... | ... | ... | ... | ... | ... |
## Changes made
- <source asset> → <optimized replacements in final paths>
- <reference file> → <old path replaced with optimized markup>
- Deleted: <old assets, temp folders, unused variants, pass logs>
## Validation
- <commands or manual checks>
- Scanner rerun: <clean confirmation or documented exceptions>
## Notes / follow-ups
- <remote assets, CMS/CDN settings, caching headers, visual QA needs>
Reference files
- delivery-strategy.md — LCP, fetchpriority, lazy loading, preload/preconnect, content-visibility, CLS, caching, measurement.
- responsive-images.md —
img, picture, srcset, sizes, LCP images, accessibility, SEO.
- video.md — codecs, ffmpeg recipes, posters,
<video> markup, lazy patterns, streaming decision.
- animated-media.md — GIF replacement with video or animated WebP/AVIF, reduced motion.
- fonts.md — WOFF2, subsetting, font-display, preload, fallback metrics, variable fonts.
- svg.md — SVGO, delivery trade-offs, currentColor.
- sharp-cli.md — Sharp CLI commands, quality table, naming, conversion policy.
- frameworks.md — Next.js, React/Vite, Astro, SvelteKit, Nuxt asset/font/video handling, CDN constraints.
1---2name: web-assets-optimization3description: Audits and optimizes all web-app assets for performance: images (Sharp AVIF/WebP variants, responsive `img`/`picture`/`srcset`/`sizes`), video (ffmpeg H.264/VP9/AV1 encoding, posters, lazy loading), animated GIF replacement with video or animated WebP/AVIF, web fonts (WOFF2 subsetting, font-display, preload), and SVG (SVGO, sprites, currentColor). Builds a per-asset delivery strategy from importance, viewport placement, and LCP status (fetchpriority, lazy loading, preload, caching, CLS). Use when auditing asset weight, fixing LCP/CLS, compressing video, replacing GIFs, subsetting fonts, or adapting asset markup for Next.js, React, Vite, Astro, SvelteKit, or Nuxt.4---56# Web Assets Optimization78Optimize the assets the app actually ships: images, video, animated media, fonts, and SVG. Prefer safe, measurable wins: smaller files, modern formats, responsive markup, stable layout, deliberate loading order, and framework-compatible delivery.910Leading words for this skill:1112- **pipeline** — extend the project's existing asset tooling; do not replace a working optimizer.13- **clean** — write final asset paths only; leave no temp folders, unused variants, orphaned heavy sources, or ffmpeg two-pass logs.14- **strategy** — every asset gets a deliberate decision from the matrix in Step 3 (importance x placement x loading context); no blanket policies.15- **LCP** — treat LCP candidates (hero image, video poster, CSS background) as highest priority; never lazy-load them.1617## When not to use1819- The user wants generated artwork from prompts → use an image-generation skill.20- The ask is a visual redesign with no asset-performance work.21- Assets are already transformed by a CMS/CDN pipeline, unless the task is markup or config around that CDN.22- Video editing/production work (cuts, color grading, subtitles) — this skill only compresses and delivers.23- Sources are proprietary, or destructive edits are requested without backups.2425## Hard rules2627- Stay **clean**: optimized files land in kept public/assets paths; remove old unoptimized sources once unreferenced; delete unused variants, experimental output dirs, and ffmpeg `-passlogfile` logs before finishing.28- Optimize referenced assets first; unused archives only if the user asks (or delete them when clearly safe).29- Detect the framework before patching markup; prefer framework-native primitives already in use.30- Do not add a package manager or root tooling to documentation-only repositories; in target apps, prefer existing package managers and scripts.31- Never lazy-load an LCP candidate — including video posters and CSS background LCP images.32- Always reserve space: `width`/`height` (or CSS `aspect-ratio`) on every `<img>` AND `<video>`.33- Video: never ship AV1-only; always keep an MP4/H.264 fallback `<source>`; H.264 requires `-pix_fmt yuv420p`, even dimensions, and `-movflags +faststart`.34- Animated GIFs used in pages: replace with `<video>` or animated WebP/AVIF unless the context only accepts image formats (README/Markdown renderers, email, Open Graph).35- Fonts: ship WOFF2 only; font preloads always carry `crossorigin`, even same-origin.36- Keep SVG logos/icons as vectors; when running SVGO, keep the `viewBox` (`removeViewBox: false`).37- Preserve semantics: meaningful `alt` for content images, `alt=""` only when decorative; `aria-label` or nearby text for meaningful `<video>` (no `alt` exists).3839## Branches4041| Ask | Path |42| --- | --- |43| Audit only | Steps 1–3, then report (skip generation and markup). |44| Optimize + markup | Full workflow below. |45| Single asset type (e.g. "compress this video", "fix our fonts", "replace these GIFs") | Steps 1–3 scoped to that type, then the matching reference file and Steps 5–9 for those assets. |46| CDN/CMS already transforms | Step 1 + markup/config only; see [frameworks.md](./frameworks.md) CMS section. |47| Delivery/LCP tuning only (no re-encoding) | Steps 1–3 + Step 6 with [delivery-strategy.md](./delivery-strategy.md). |4849## Workflow5051Track with this checklist (one item per step below):5253```text54- [ ] 1. Detect framework and asset pipeline55- [ ] 2. Inventory referenced assets (images, video, GIFs, fonts, SVG)56- [ ] 3. Build the per-asset strategy matrix57- [ ] 4. Plan per-type transforms and commands58- [ ] 5. Generate optimized assets into final paths59- [ ] 6. Replace references with optimized markup and loading attributes60- [ ] 7. Delete old unoptimized assets and unused variants61- [ ] 8. Re-scan and verify references stay clean62- [ ] 9. Verify visuals/build and report63```6465### Scripts6667Resolve this skill's directory (the folder that contains this `SKILL.md`), then run helpers relative to that root. Pass `--root` as the target app path.6869```bash70python3 <skill-dir>/scripts/scan-assets.py --root <app-root> --format markdown71python3 <skill-dir>/scripts/scan-assets.py --root <app-root> --format json > asset-scan.json72python3 <skill-dir>/scripts/generate-sharp-plan.py asset-scan.json --widths 320,640,1024,153673python3 <skill-dir>/scripts/generate-ffmpeg-plan.py asset-scan.json74```7576Inside this repository only, `<skill-dir>` is `skills/web/web-assets-optimization`.7778### Step 1 — Detect framework and pipeline7980Check `package.json`, framework configs, image/font component imports (`next/image`, `next/font`, `astro:assets`, `@nuxt/image`, `@nuxt/fonts`, `@sveltejs/enhanced-img`), and CDN/CMS loaders. If a working **pipeline** exists, extend it.8182Done when: the framework (or plain HTML) plus any existing optimizers for images AND fonts are named, and the patch strategy is chosen (native component vs plain markup).8384See [frameworks.md](./frameworks.md).8586### Step 2 — Inventory referenced assets8788Run `scan-assets.py`. Review per type: path, bytes, dimensions (video dims are not detected — note as unknown), references, and usage hints (logo, app-icon, favicon, animated, poster, preload, legacy-font-format).8990Done when: every referenced local image/video/GIF/font/SVG under `--root` is listed; large unreferenced assets are noted; remote URLs are listed separately.9192### Step 3 — Build the strategy matrix9394This is the intelligence step — do not skip it. For each asset that matters (raster > 200 KB, any video, any GIF > 100 KB, every shipped font file, SVG > 20 KB, plus every LCP candidate regardless of size), record one **strategy** row: `asset | type | role | placement | action | loading policy`.9596- Role: LCP candidate / contentful / decorative / functional (icon, favicon).97- Placement: above fold / below fold / offscreen-conditional (modal, carousel, tab) / global (font, sprite).98- LCP candidates include: `<img>`, `<video>` poster, CSS `background-image` via `url()`, `<image>` in `<svg>`.99100Loading policy per role x placement:101102| Role x placement | Loading policy |103| --- | --- |104| LCP candidate | eager, `fetchpriority="high"`, never lazy; must be discoverable in initial HTML; preload only if late-discovered (CSS background, font) |105| Above-fold, non-LCP | eager, default priority, dimensions set |106| Below-fold image/iframe | `loading="lazy"`, `decoding="async"`, dimensions mandatory |107| Offscreen-conditional (modal, carousel, tab) | `loading="lazy"` (or defer the fetch until the trigger interaction for modal/tab content); `fetchpriority="low"` on prefetched carousel slides so they do not compete with critical resources |108| Below-fold autoplay video loop | `preload="none"` + poster, or IntersectionObserver src-swap; pause offscreen |109| Click-to-play video | `preload="metadata"` (or `none`) + poster |110| Long offscreen sections | `content-visibility: auto` + `contain-intrinsic-size` |111| Critical text font | WOFF2 subset, preload with `crossorigin`, `font-display: swap`/`fallback` + metric-adjusted fallback |112| Secondary/decorative font | `font-display: optional`, no preload |113| Animated GIF in page | replace per [animated-media.md](./animated-media.md), reduced-motion fallback |114115Order of work: **LCP** candidate → oversized above-fold assets → video/GIF byte hogs → fonts → below-fold rasters → SEO/social images. Details and rationale: [delivery-strategy.md](./delivery-strategy.md).116117Done when: every qualifying asset has a matrix row and the LCP candidate is identified (or its absence stated).118119### Step 4 — Plan per-type transforms120121Dispatch per type; read the reference file before planning that type:122123- Raster images → [sharp-cli.md](./sharp-cli.md) + `generate-sharp-plan.py`. Default format policy unless the project constrains otherwise: photos AVIF + WebP + JPEG fallback; UI screenshots WebP + PNG when lossless detail matters; transparent rasters WebP/AVIF with PNG fallback; logos/icons stay SVG; Open Graph/social images JPEG/PNG at platform dimensions.124- Video → [video.md](./video.md) + `generate-ffmpeg-plan.py` (MP4 + WebM + poster).125- GIF/animated → [animated-media.md](./animated-media.md) + `generate-ffmpeg-plan.py`.126- Fonts → [fonts.md](./fonts.md) (WOFF2 conversion, subsetting, display strategy).127- SVG → [svg.md](./svg.md) (SVGO plan, delivery method).128129Done when: each matrix row has planned commands or a stated no-op reason.130131### Step 5 — Generate into final paths132133Review the generated command plans, then run them. Target **final** kept paths (avoid `--out-dir` unless you will move winners into final paths and delete the temp dir before finishing). Produce only variants the app will reference; delete ffmpeg pass logs.134135Done when: optimized files exist at final paths for every planned row and no temp output or log remains.136137### Step 6 — Replace references138139Apply the matrix's loading policy while patching markup. Before editing image markup, load [responsive-images.md](./responsive-images.md) and apply every Core rule; for video/GIF markup follow [video.md](./video.md) and [animated-media.md](./animated-media.md); for `@font-face`/preload edits follow [fonts.md](./fonts.md); for priorities/preload/caching follow [delivery-strategy.md](./delivery-strategy.md).140141Also update manifests, favicons, Open Graph, CSS `url(...)`, MDX/Markdown, and structured data when they point at old files. Prefer framework-native patterns when the **pipeline** uses them.142143Done when: no runtime reference still points at an unoptimized original that has a replacement, and every touched element carries the loading attributes its matrix row prescribes (or exceptions are documented).144145### Step 7 — Delete old assets146147Remove unreferenced heavy originals (rasters, videos, GIFs, legacy font formats), unused generated variants, and any temp output directory. Keep SVG sources and any source files the user asked to retain.148149Done when: the tree is **clean** — only referenced optimized assets, intentional vectors, and documented kept sources remain.150151### Step 8 — Re-scan and verify cleanliness152153Rerun `scan-assets.py`. Confirm optimized references are in place, no orphaned heavy assets remain, and no legacy font formats are still shipped.154155Done when: the rescan matches the **clean** bar above.156157### Step 9 — Verify and report158159Check desktop/mobile render, sensible `srcset` picks, video plays with correct source order and poster, autoplay loops are muted + `playsinline`, fonts render without layout jumps, no CLS from missing dimensions, LCP not lazy-loaded, and the build passes with the project's existing commands. Compare bytes before/after per type.160161Done when: validation notes are recorded and the report below is filled.162163## Output format164165```markdown166## Web assets optimization summary167168- Framework/pipeline detected: <framework and asset tooling>169- Assets scanned: <count> (<images/videos/GIFs/fonts/SVG counts>); bytes before → after: <totals>170- LCP candidate: <asset and treatment>171- Highest-priority fixes: <short list>172173## Strategy matrix174175| Asset | Type | Role | Placement | Action | Loading policy |176| --- | --- | --- | --- | --- | --- |177| ... | ... | ... | ... | ... | ... |178179## Changes made180181- <source asset> → <optimized replacements in final paths>182- <reference file> → <old path replaced with optimized markup>183- Deleted: <old assets, temp folders, unused variants, pass logs>184185## Validation186187- <commands or manual checks>188- Scanner rerun: <clean confirmation or documented exceptions>189190## Notes / follow-ups191192- <remote assets, CMS/CDN settings, caching headers, visual QA needs>193```194195## Reference files196197- [delivery-strategy.md](./delivery-strategy.md) — LCP, fetchpriority, lazy loading, preload/preconnect, content-visibility, CLS, caching, measurement.198- [responsive-images.md](./responsive-images.md) — `img`, `picture`, `srcset`, `sizes`, LCP images, accessibility, SEO.199- [video.md](./video.md) — codecs, ffmpeg recipes, posters, `<video>` markup, lazy patterns, streaming decision.200- [animated-media.md](./animated-media.md) — GIF replacement with video or animated WebP/AVIF, reduced motion.201- [fonts.md](./fonts.md) — WOFF2, subsetting, font-display, preload, fallback metrics, variable fonts.202- [svg.md](./svg.md) — SVGO, delivery trade-offs, currentColor.203- [sharp-cli.md](./sharp-cli.md) — Sharp CLI commands, quality table, naming, conversion policy.204- [frameworks.md](./frameworks.md) — Next.js, React/Vite, Astro, SvelteKit, Nuxt asset/font/video handling, CDN constraints.