--- name: optimizing-images description: 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. --- # 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:** ```bash find public -type f \( -name "*.jpg" -o -name "*.png" -o -name "*.gif" \) -size +100k -exec ls -lh {} \; ``` **Check image dimensions:** ```bash # Requires ImageMagick find public -type f \( -name "*.jpg" -o -name "*.png" \) -exec identify -format "%f: %wx%h (%b)\n" {} \; ``` **Detect unoptimized in HTML:** ```bash grep -rn --include="*.tsx" --include="*.html" ' // Fill container
Hero description
// With placeholder blur import heroImage from '@/public/images/hero.jpg'; Hero description ``` **next.config.js for external images:** ```javascript 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:** ```vue ``` ```typescript // 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:** ```html Description ``` **Intersection Observer (custom):** ```typescript // 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(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):** ```tsx // Hero images, LCP candidates Hero // Or in HTML Hero ``` ### Step 7: Responsive Breakpoints **Calculate optimal breakpoints:** ```typescript // 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(); 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:** ```typescript // 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", }), }), ], }; ``` ```tsx // 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:** ```yaml 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 ```bash # 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](https://squoosh.app/) - Browser-based comparison - [Sharp Documentation](https://sharp.pixelplumbing.com/) - [Next.js Image Optimization](https://nextjs.org/docs/app/building-your-application/optimizing/images) - [web.dev Image Optimization](https://web.dev/learn/images/)