skills/image-optimization-engineer/SKILL.md
Optimize web images for performance with Next.js Image, responsive srcset, AVIF/WebP, lazy loading, and blur placeholders. Activate on: image optimization, LCP improvement, responsive images, AVIF, blur placeholder, next/image. NOT for: image generation/editing (use image-gen skills), SVG icon systems (use design-system-creator).
npx skillsauth add curiositech/windags-skills image-optimization-engineerInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Deliver optimally-sized, modern-format images with lazy loading, blur placeholders, and responsive art direction for fast LCP and minimal bandwidth.
Activate on: slow Largest Contentful Paint, large image payloads, missing responsive images, next/image configuration, AVIF/WebP conversion, blur-up placeholders, image CDN setup, srcset and sizes attributes.
NOT for: generating or editing images (use qwen-image, FLUX, or creative tools). SVG icon sprite systems -- use design-system-creator. Video optimization -- different domain entirely.
next/image -- automatic AVIF/WebP serving, lazy loading, blur placeholders, and responsive srcset.sizes -- tell the browser the rendered width so it picks the right srcset candidate.plaiceholder or Next.js built-in placeholder="blur" for static imports.| Domain | Technologies | Key Patterns |
|--------|-------------|--------------|
| Framework Integration | next/image, Astro <Image>, Vite vite-imagetools | Automatic optimization at build/request time |
| Modern Formats | AVIF, WebP, fallback JPEG/PNG | Content negotiation via Accept header |
| Responsive Images | srcset, sizes, <picture> | Art direction, resolution switching |
| Lazy Loading | loading="lazy", Intersection Observer | Defer off-screen images |
| Placeholders | LQIP (blur), solid color, blurhash | Perceived performance during load |
| CDN/Loaders | Cloudflare Images, Imgix, Cloudinary | On-the-fly resize, format conversion, caching |
import Image from 'next/image';
// Static import -- enables automatic blur placeholder
import heroImage from '@/public/images/hero.jpg';
export function HeroSection() {
return (
<Image
src={heroImage}
alt="Product hero shot showing the dashboard in action"
placeholder="blur" // automatic blurhash from static import
priority // preload -- this is the LCP element
sizes="100vw" // full-width hero
quality={85}
className="w-full h-auto object-cover"
/>
);
}
export function ProductGrid({ products }: { products: Product[] }) {
return (
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
{products.map(product => (
<Image
key={product.id}
src={product.imageUrl}
alt={product.name}
width={400}
height={400}
sizes="(max-width: 768px) 50vw, (max-width: 1024px) 33vw, 25vw"
loading="lazy" // NOT priority -- below the fold
placeholder="blur"
blurDataURL={product.blurHash}
/>
))}
</div>
);
}
<picture>When you need different crops/aspect ratios at different breakpoints:
export function ResponsiveHero({ image }: { image: HeroImage }) {
return (
<picture>
{/* Mobile: square crop */}
<source
media="(max-width: 639px)"
srcSet={`${image.mobileUrl}?w=640&format=avif 1x, ${image.mobileUrl}?w=1280&format=avif 2x`}
type="image/avif"
/>
<source
media="(max-width: 639px)"
srcSet={`${image.mobileUrl}?w=640&format=webp 1x, ${image.mobileUrl}?w=1280&format=webp 2x`}
type="image/webp"
/>
{/* Desktop: wide crop */}
<source
media="(min-width: 640px)"
srcSet={`${image.desktopUrl}?w=1200&format=avif 1x, ${image.desktopUrl}?w=2400&format=avif 2x`}
type="image/avif"
/>
<source
media="(min-width: 640px)"
srcSet={`${image.desktopUrl}?w=1200&format=webp 1x, ${image.desktopUrl}?w=2400&format=webp 2x`}
type="image/webp"
/>
{/* Fallback */}
<img
src={`${image.desktopUrl}?w=1200&format=jpeg`}
alt={image.alt}
loading="eager"
decoding="async"
className="w-full h-auto"
/>
</picture>
);
}
┌─ Source Image (4000x3000 JPEG, 8MB) ──────────────┐
│ │
│ Build/CDN Pipeline: │
│ ├─ Resize: 640, 960, 1280, 1920, 2560 │
│ ├─ Format: AVIF (best), WebP (fallback), JPEG │
│ ├─ Quality: 80 (AVIF), 85 (WebP), 85 (JPEG) │
│ └─ Generate: blur placeholder (32x32 base64) │
│ │
│ Browser receives (via content negotiation): │
│ ├─ Chrome/Edge: AVIF @ matched width (~40KB) │
│ ├─ Safari 16+: WebP @ matched width (~60KB) │
│ └─ Legacy: JPEG @ matched width (~90KB) │
│ │
│ Result: 8MB → 40-90KB (98% reduction) │
└─────────────────────────────────────────────────────┘
sizes attribute -- without it, the browser assumes the image is 100vw and downloads the largest srcset candidate. A 25vw grid image downloads 4x too large.priority on every image -- preloads them all, defeating the purpose. Only the LCP element (usually the hero image) should have priority.width and height -- causes Cumulative Layout Shift (CLS) because the browser cannot reserve space before the image loads. Always set dimensions or use aspect-ratio.priority (or fetchpriority="high") and is preloadedsizes attribute matching their actual rendered width at each breakpointType column)width and height set on every <img> (CLS score = 0 for images)loading="lazy" (not priority)decoding="async" set on non-critical imagesnext/image handles format negotiation (no manual <picture> unless art direction needed)data-ai
license: Apache-2.0 NOT for unrelated tasks outside this domain.
development
Use when designing caching strategies (cache-aside, write-through, write-behind), implementing distributed locks, building rate limiters, leaderboards, real-time streams (XADD/consumer groups), pub/sub, or tuning eviction policies. Triggers: thundering-herd on cache miss, dogpile on key expiry, Redlock vs SET-NX-PX choice, sliding-window rate limiter, hot-key on a single cluster slot, big-key blowup, MULTI/EXEC across slots, KEYS in production. NOT for Redis Cluster operations/admin (different domain), embedded KV (SQLite, leveldb), in-process LRU caches, or Memcached.
tools
Drawing the `'use client'` boundary correctly in React Server Components apps (Next.js App Router, RSC frameworks) — leaf-pushing, slot composition, serialization rules, and environment poisoning prevention. Grounded in react.dev and Next.js 16 docs.
development
Use when designing rate limiting for an API, choosing between token bucket / sliding window / leaky bucket / fixed window, implementing it in Redis, deciding edge (Cloudflare/Upstash) vs origin enforcement, sizing per-user vs per-IP vs per-endpoint quotas, returning the right 429 response with Retry-After, or fixing the boundary-burst bug in fixed-window limiters. Triggers: 429 too many requests, INCR + EXPIRE, ZADD + ZREMRANGEBYSCORE + ZCARD, X-RateLimit-Remaining header, Cloudflare WAF rate limiting rules, Upstash @upstash/ratelimit, leaky bucket shaping vs policing, distributed rate limiter consistency. NOT for DDoS mitigation specifically (different scale), CAPTCHA / bot management, full WAF design, or per-user quota billing.