{"id":"07wtnex2bcbkqp3","title":"WebP and AVIF: How I Ship Images Without Regret","slug":"webp-avif-modern-image-delivery","summary":"JPEG is not dead but it is not the default either. I pair modern formats with dimensions, lazy loading, and honest LCP preloads.","imageUrl":"https://briancrabtree.me/images/journal-webp-avif-modern-image-delivery.webp","category":"Performance","date":"2026-02-09T18:00:00.000Z","featured":false,"likes":34,"author":"Brian Crabtree","content":"<h2>Format choice without religion</h2>\n\n<p>AVIF wins on bytes for photos at similar quality. WebP is wider supported and faster to encode in bulk pipelines. I serve picture elements with AVIF first, WebP fallback, JPEG or PNG last for ancient clients. SVG stays vector for logos and icons.</p>\n\n<p>Do not convert screenshots with thin text to lossy AVIF without checking legibility. PNG or lossless WebP may be smaller visually for UI captures.</p>\n\n<p>Art direction sometimes needs different crop per breakpoint, not only width. picture with source media attributes handles crop; srcset alone only swaps resolution.</p>\n\n<pre><code>&lt;picture&gt;\n  &lt;source srcset=\"/img/hero.avif\" type=\"image/avif\"&gt;\n  &lt;source srcset=\"/img/hero.webp\" type=\"image/webp\"&gt;\n  &lt;img src=\"/img/hero.jpg\" alt=\"\" width=\"800\" height=\"450\"&gt;\n&lt;/picture&gt;</code></pre>\n\n<p><figure>\n  <img src=\"/images/journal-inline-webp-avif-delivery.webp\" alt=\"File size comparison bar chart of JPEG versus WebP versus AVIF for same image\" width=\"1200\" height=\"675\" loading=\"lazy\" />\n  <figcaption>Modern formats cut bytes without visible quality loss — if fallbacks are wired.</figcaption>\n</figure></p>\n\n<h2>Dimensions beat format obsession</h2>\n\n<p>A 2400px JPEG squeezed to AVIF still wastes bandwidth on mobile. I generate widths: 640, 960, 1280, 1920 and let srcset pick. width and height attributes reserve space so CLS stays near zero when the file arrives late.</p>\n\n<p>Hero images get fetchpriority high and link rel preload only when they are the LCP element. Preloading every carousel slide is self-sabotage.</p>\n\n<p>CMS plugins that auto-convert on upload still need quality sliders per collection. Hero photos and team headshots should not share one quality preset.</p>\n\n<h2>Encoding pipeline I maintain</h2>\n\n<p>Build step or upload hook runs sharp, squoosh, or cwebp with documented quality targets. I store masters, ship derivatives, and never let CMS uploads dump 4 MB originals into public folders. WordPress sites get the same rule via plugin hooks or offline batch scripts.</p>\n\n<p>Journal headers on this site are 1600 by 900 WebP at quality 85. That is the contract designers export against.</p>\n\n<p>I store dominant color in metadata for placeholder backgrounds while images load. That reduces perceived wait even when bytes are already optimized.</p>\n\n<h2>Lazy loading and above-fold rules</h2>\n\n<p>loading lazy on the LCP image is a common mistake copied from tutorials. First visible image is eager with dimensions set. Below-fold galleries lazy load. Intersection observers are backup when markup is CMS-driven and editors forget.</p>\n\n<p>OG images for social sharing are a separate size contract from LCP heroes. Do not reuse 1200px OG as uncropped mobile LCP without testing.</p>\n\n<p>When I audit performance stacks, Lazy loading and above-fold rules is where hours disappear if nobody owns the outcome. I write before-and-after notes, attach PSI or waterfall screenshots, and leave the team a three-item follow-up list. Reproducible wins beat toolchain debates in sprint review. If this section matches your bottleneck, send the URL and whether LCP, INP, CLS, or crawl coverage is the pain point.</p>\n\n<h2>CDN and cache headers</h2>\n\n<p>Immutable cache filenames or hashed paths let max-age be aggressive. Transform at edge only when you trust the CDN image optimizer; otherwise bake sizes at build. I verify Content-Type matches bytes, not extension lies.</p>\n\n<p>Responsive images plus HTTP/2 multiplexing beat one giant progressive JPEG on most pages. The exception is tiny icons where SVG or inline wins.</p>\n\n<p>SVG filters and blurs on huge heroes are GPU expensive. Photography plus light gradient overlay beats heavy filter stacks.</p>\n\n<h2>Measuring impact</h2>\n\n<p>Lighthouse LCP element URL tells you which file actually painted. I compare transferred bytes and decode time in DevTools network, not just file size on disk. A smaller AVIF that decodes slowly on old Android can lose to a moderate WebP.</p>\n\n<p>Publish before and after PSI links when clients ask for proof. Numbers move between runs; trends over deploys matter.</p>\n\n<p>Image CDNs that resize on the fly need width caps in URL params to prevent abuse. Someone will request 8000px if you allow it.</p>\n\n<h2>Practical next step</h2>\n\n<p>Export one hero three ways, wire picture, measure mobile LCP. If it drops, roll the pattern site-wide. I automate the boring widths; you keep creative control of crop and subject.</p>\n\n<p>After deploy I spot-check decode in Chrome Performance memory. Oversized AVIF decode on low RAM Android is a real regression class.</p>\n\n<p>When I audit performance stacks, Practical next step is where hours disappear if nobody owns the outcome. I write before-and-after notes, attach PSI or waterfall screenshots, and leave the team a three-item follow-up list. Reproducible wins beat toolchain debates in sprint review. If this section matches your bottleneck, send the URL and whether LCP, INP, CLS, or crawl coverage is the pain point. For a related angle I keep coming back to, see <a href=\"/journal/image-srcset-lcp-sizing/\">Image srcset and Sizing for LCP (Stop Shipping Desktop Pixels to Mobile)</a>.</p>","tags":["webp","avif","images"],"views":77}