Responsive images (WebP/AVIF renditions) + media usage tracking

Responsive images (WebP/AVIF renditions) + media usage tracking

#19 in tonybierman/dx-blog — merged 2026-05-30

What & why

WordPress-style image handling: every uploaded image now gets a set of resized renditions served via <picture>/srcset, so the browser fetches the smallest file and best format that fits each slot instead of one full-size original. Plus media usage tracking ("which posts use this image").

Changes

  • Renditions on uploadthumb 320 / small 640 / medium 1024 / large 1600 (only widths below the original — never upscaling) + a full-size re-encode, in WebP. Pipeline in src/server/images.rs (decode → Lanczos resize → encode), run on a blocking thread. Best-effort: animated GIFs / undecodable uploads keep only the original and never fail the upload.
  • Schema — new media_variants table (CREATE TABLE IF NOT EXISTS, idempotent). Originals stay the canonical media.url.
  • Responsive rendering — featured images (post detail + feed cards) and inline /uploads body images render as <picture> with format-switching srcset (AVIF→WebP→<img> fallback). Shared ResponsiveImg component; inline rewrite happens at read time in get_post. External URLs stay a plain <img>.
  • Usage tracking — media library shows "Used in N posts" and a delete confirm listing the posts (cover vs. body). Derived from featured-URL + body_md match, so it can't drift — hence no featured_media_id column.
  • AVIF — behind the optional avif cargo feature (off by default; heavy build/encode). --features avif also emits .avif and prepends an <source type="image/avif">.
  • BackfillDX_BACKFILL_IMAGES=1 at startup regenerates renditions for pre-existing uploads (idempotent).

With AVIF off (default), images render to WebP

<picture>
  <source type="image/webp" srcset="…-thumb.webp 320w, …-large.webp 1600w" sizes="…">
  <img src="/uploads/3_photo.jpg" loading="lazy" decoding="async">
</picture>

Verification

All gates pass: cargo fmt --check; clippy on server + wasm targets with -D warnings; cargo test (incl. 5 new server::images tests); and cargo check --features …,avif. Manual E2E steps in TODO_VERIFICATION.md.

🤖 Generated with Claude Code

Last updated 2026-05-31