Claude-skill-registry optimizing-images
Optimizes images and generates responsive markup. Use when the user asks about image formats (WebP, AVIF), srcset, responsive images, Next.js Image, or reducing image file sizes.
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/image-optimizer" ~/.claude/skills/majiayu000-claude-skill-registry-optimizing-images && rm -rf "$T"
manifest:
skills/data/image-optimizer/SKILL.mdsource content
Image Optimization Assistant
When to use this skill
- User asks about image optimization
- User mentions WebP, AVIF, or modern formats
- User wants responsive images or srcset
- User asks about Next.js Image or picture element
- User wants to reduce page weight from images
Workflow
- Audit current images
- Identify optimization opportunities
- Convert to modern formats
- Generate responsive markup
- Implement lazy loading
- Validate improvements
Instructions
Step 1: Audit Current Images
Find large images:
find public -type f \( -name "*.jpg" -o -name "*.png" -o -name "*.gif" \) -size +100k -exec ls -lh {} \;
Check image dimensions:
# Requires ImageMagick find public -type f \( -name "*.jpg" -o -name "*.png" \) -exec identify -format "%f: %wx%h (%b)\n" {} \;
Detect unoptimized in HTML:
grep -rn --include="*.tsx" --include="*.html" '<img' src/ | grep -v 'loading='
Step 2: Format Selection
| Format | Use Case | Browser Support |
|---|---|---|
| WebP | Photos, general use | 97%+ |
| AVIF | Best compression, photos | 92%+ |
| PNG | Transparency, icons | 100% |
| SVG | Icons, logos, illustrations | 100% |
| JPEG | Legacy fallback | 100% |
Compression comparison (typical):
| Original | WebP | AVIF |
|---|---|---|
| 500KB JPEG | ~300KB (-40%) | ~200KB (-60%) |
| 200KB PNG | ~80KB (-60%) | ~50KB (-75%) |
Step 3: Convert Images
Using Sharp (Node.js):
npm install sharp
// scripts/optimize-images.ts import sharp from "sharp"; import { glob } from "glob"; import path from "path"; const QUALITY = { webp: 80, avif: 65 }; const SIZES = [640, 750, 828, 1080, 1200, 1920]; async function optimizeImage(inputPath: string): Promise<void> { const dir = path.dirname(inputPath); const name = path.basename(inputPath, path.extname(inputPath)); for (const width of SIZES) { const image = sharp(inputPath).resize(width); // WebP await image .webp({ quality: QUALITY.webp }) .toFile(path.join(dir, `${name}-${width}.webp`)); // AVIF await image .avif({ quality: QUALITY.avif }) .toFile(path.join(dir, `${name}-${width}.avif`)); } console.log(`Optimized: ${inputPath}`); } async function main() { const images = await glob("public/images/**/*.{jpg,jpeg,png}"); await Promise.all(images.map(optimizeImage)); } main();
Using CLI tools:
# WebP conversion npx @aspect/image-optimize --format webp --quality 80 public/images/*.jpg # Using cwebp directly cwebp -q 80 input.jpg -o output.webp # AVIF with avif-cli npx avif --input public/images/*.jpg --quality 65
Step 4: Responsive Image Markup
HTML picture element:
<picture> <!-- AVIF for browsers that support it --> <source type="image/avif" srcset=" /images/hero-640.avif 640w, /images/hero-1080.avif 1080w, /images/hero-1920.avif 1920w " sizes="(max-width: 768px) 100vw, 50vw" /> <!-- WebP fallback --> <source type="image/webp" srcset=" /images/hero-640.webp 640w, /images/hero-1080.webp 1080w, /images/hero-1920.webp 1920w " sizes="(max-width: 768px) 100vw, 50vw" /> <!-- JPEG fallback for old browsers --> <img src="/images/hero-1080.jpg" alt="Hero image description" width="1920" height="1080" loading="lazy" decoding="async" /> </picture>
Sizes attribute guide:
| Layout | Sizes Value |
|---|---|
| Full width | |
| Half width on desktop | |
| Fixed width | |
| Grid (3 columns) | |
Step 5: Framework Integration
Next.js Image:
import Image from 'next/image'; // Basic usage - automatic optimization <Image src="/images/hero.jpg" alt="Hero description" width={1920} height={1080} priority // For above-the-fold images /> // Fill container <div className="relative h-64"> <Image src="/images/hero.jpg" alt="Hero description" fill className="object-cover" sizes="(max-width: 768px) 100vw, 50vw" /> </div> // With placeholder blur import heroImage from '@/public/images/hero.jpg'; <Image src={heroImage} alt="Hero description" placeholder="blur" priority />
next.config.js for external images:
module.exports = { images: { remotePatterns: [ { protocol: "https", hostname: "cdn.example.com", }, ], formats: ["image/avif", "image/webp"], deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048], imageSizes: [16, 32, 48, 64, 96, 128, 256, 384], }, };
Nuxt Image:
<template> <NuxtImg src="/images/hero.jpg" alt="Hero description" width="1920" height="1080" format="webp" loading="lazy" sizes="sm:100vw md:50vw lg:33vw" /> <!-- With picture for art direction --> <NuxtPicture src="/images/hero.jpg" alt="Hero description" sizes="sm:100vw md:50vw" :imgAttrs="{ class: 'rounded-lg' }" /> </template>
// nuxt.config.ts export default defineNuxtConfig({ modules: ["@nuxt/image"], image: { format: ["avif", "webp"], screens: { sm: 640, md: 768, lg: 1024, xl: 1280, }, }, });
Step 6: Lazy Loading
Native lazy loading:
<img src="/image.jpg" alt="Description" loading="lazy" decoding="async" />
Intersection Observer (custom):
// hooks/useLazyImage.ts import { useEffect, useRef, useState } from "react"; export function useLazyImage(src: string) { const [isLoaded, setIsLoaded] = useState(false); const [isInView, setIsInView] = useState(false); const imgRef = useRef<HTMLImageElement>(null); useEffect(() => { const observer = new IntersectionObserver( ([entry]) => { if (entry.isIntersecting) { setIsInView(true); observer.disconnect(); } }, { rootMargin: "50px" }, ); if (imgRef.current) observer.observe(imgRef.current); return () => observer.disconnect(); }, []); return { imgRef, isInView, isLoaded, setIsLoaded }; }
Above-the-fold images (don't lazy load):
// Hero images, LCP candidates <Image src="/hero.jpg" alt="Hero" priority /> // Or in HTML <img src="/hero.jpg" alt="Hero" fetchpriority="high" />
Step 7: Responsive Breakpoints
Calculate optimal breakpoints:
// scripts/calculate-breakpoints.ts const VIEWPORT_WIDTHS = [320, 375, 414, 768, 1024, 1280, 1440, 1920]; const DEVICE_PIXEL_RATIOS = [1, 2, 3]; function calculateBreakpoints( imageWidth: number, containerRatio: number = 1, // 1 = full width, 0.5 = half width ): number[] { const breakpoints = new Set<number>(); for (const viewport of VIEWPORT_WIDTHS) { for (const dpr of DEVICE_PIXEL_RATIOS) { const width = Math.round(viewport * containerRatio * dpr); if (width <= imageWidth) { breakpoints.add(width); } } } return Array.from(breakpoints).sort((a, b) => a - b); } // Example: 1920px image at half viewport console.log(calculateBreakpoints(1920, 0.5)); // [160, 188, 207, 320, 375, 384, 414, 512, 621, 640, 768, 960]
Step 8: Build Pipeline Integration
Vite plugin:
// vite.config.ts import { imagetools } from "vite-imagetools"; export default { plugins: [ imagetools({ defaultDirectives: new URLSearchParams({ format: "webp;avif;jpg", w: "640;1280;1920", quality: "80", }), }), ], };
// Usage with query params import heroSrcset from "./hero.jpg?w=640;1280;1920&format=webp&as=srcset"; import heroAvif from "./hero.jpg?w=1280&format=avif";
GitHub Action for optimization:
name: Optimize Images on: pull_request: paths: - "public/images/**" jobs: optimize: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Optimize images uses: calibreapp/image-actions@main with: githubToken: ${{ secrets.GITHUB_TOKEN }} jpegQuality: "80" webpQuality: "80"
Optimization Checklist
| Check | Target |
|---|---|
| Format | WebP/AVIF with JPEG fallback |
| Dimensions | No larger than 2x display size |
| File size | < 200KB for hero, < 100KB for cards |
| Lazy loading | All images below fold |
| Explicit dimensions | width/height on all images |
| Alt text | Descriptive on all images |
Validation
Before completing:
- Images converted to WebP/AVIF
- Responsive srcset generated
- Lazy loading on below-fold images
- Priority on LCP image
- Width/height prevent layout shift
- Total image weight reduced
# Check image sizes npx @unlighthouse/cli --site http://localhost:3000 # Lighthouse npx lighthouse http://localhost:3000 --only-categories=performance
Error Handling
- Sharp installation fails: Install build tools; use prebuilt binaries.
- AVIF encoding slow: Use lower effort setting or limit to key images.
- CDN not serving modern formats: Check Accept header handling and cache config.
- Layout shift on load: Always include width/height or aspect-ratio.
Resources
- Squoosh - Browser-based comparison
- Sharp Documentation
- Next.js Image Optimization
- web.dev Image Optimization