[{"data":1,"prerenderedAt":1207},["ShallowReactive",2],{"content:\u002Fimage-media-optimization\u002Fserving-avif-and-webp-with-fallbacks\u002F":3,"surroundings:\u002Fimage-media-optimization\u002Fserving-avif-and-webp-with-fallbacks\u002F":1198},{"id":4,"title":5,"body":6,"description":1180,"extension":1181,"meta":1182,"navigation":1192,"path":1193,"seo":1194,"stem":1196,"__hash__":1197},"content\u002Fimage-media-optimization\u002Fserving-avif-and-webp-with-fallbacks\u002Findex.md","Serving AVIF and WebP with Fallbacks",{"type":7,"value":8,"toc":1170},"minimark",[9,14,29,55,181,186,217,222,235,347,351,354,370,373,377,401,444,469,595,599,649,672,859,863,904,916,923,927,938,954,957,968,972,982,993,1111,1119,1123,1155,1160,1163,1166],[10,11,13],"h1",{"id":12},"serving-avif-and-webp-with-fallbacks-an-encoding-and-negotiation-workflow","Serving AVIF and WebP with Fallbacks: An Encoding and Negotiation Workflow",[15,16,17,18,23,24,28],"p",{},"This guide extends the ",[19,20,22],"a",{"href":21},"\u002Fimage-media-optimization\u002F","Image & Media Optimization"," discipline into the codec layer, where the biggest single byte reduction on most pages still lives. Re-encoding a JPEG hero as AVIF routinely cuts transfer weight by 40–60% at matched visual quality, and because that hero is usually the ",[19,25,27],{"href":26},"\u002Fcore-web-vitals-measurement\u002Fmeasuring-lcp-with-chrome-devtools\u002F","Largest Contentful Paint"," candidate, those bytes come straight off your LCP budget of 2.5s. On a mid-tier mobile connection (~1.6 Mbps), trimming a 180KB JPEG to a 75KB AVIF removes roughly 0.5s of pure transfer time before any other optimization. The catch is that no single modern codec is universally supported and freshly decoded by every client, so shipping AVIF safely means shipping a fallback chain — not picking one format and hoping.",[15,30,31,32,36,37,40,41,45,46,50,51,54],{},"The workflow here is mechanical: encode each master into AVIF and WebP at a defensible quality, declare the candidates in a ",[33,34,35],"code",{},"\u003Cpicture>"," type chain (or negotiate via the ",[33,38,39],{},"Accept"," header at the edge), tune the encoder knobs against a byte budget, and then verify that the byte win is not silently eaten by a decode cost on the main thread. Format choice is orthogonal to size selection — the resolution ladder from ",[19,42,44],{"href":43},"\u002Fimage-media-optimization\u002Fresponsive-images-with-srcset-and-sizes\u002F","responsive images with srcset and sizes"," still chooses ",[47,48,49],"em",{},"how big","; this layer chooses ",[47,52,53],{},"which codec",".",[15,56,57],{},[58,59,66,67,66,71,66,75,66,85,66,92,66,101,66,107,66,112,66,116,66,121,66,125,66,128,66,131,66,135,66,139,66,142,66,145,66,151,66,154,66,159,66,164,66,169,66,173,66,177,66],"svg",{"xmlns":60,"viewBox":61,"width":62,"role":63,"ariaLabel":64,"style":65},"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","0 0 760 340","100%","img","The picture type fallback chain: the browser walks AVIF then WebP then JPEG and uses the first source whose type it supports","height:auto;max-width:760px;display:block;margin:1.75rem auto;font-family:inherit;color:#001d3d"," ",[68,69,70],"title",{},"picture type fallback chain",[72,73,74],"desc",{},"The browser evaluates source elements top to bottom and uses the first whose type attribute names a codec it can decode, falling back to the img element.",[76,77],"rect",{"x":78,"y":78,"width":79,"height":80,"rx":81,"fill":82,"stroke":83,"style":84},"1","758","338","10","none","currentColor","stroke-opacity:0.18",[86,87,91],"text",{"x":88,"y":89,"fill":83,"style":90},"24","38","font-size:18px;font-weight:700","The picture type fallback chain",[76,93],{"x":88,"y":94,"width":95,"height":96,"rx":97,"fill":98,"stroke":99,"style":100},"62","220","70","6","#ffc300","#b8860b","fill-opacity:0.22",[86,102,106],{"x":103,"y":104,"fill":83,"style":105},"134","90","font-size:13px;font-weight:600;text-anchor:middle","source: AVIF",[86,108,111],{"x":103,"y":109,"fill":83,"style":110},"110","font-size:12px;text-anchor:middle","smallest bytes",[86,113,115],{"x":103,"y":114,"fill":83,"style":110},"126","highest decode cost",[76,117],{"x":118,"y":94,"width":95,"height":96,"rx":97,"fill":119,"stroke":119,"style":120},"270","#0466c8","fill-opacity:0.14",[86,122,124],{"x":123,"y":104,"fill":83,"style":105},"380","source: WebP",[86,126,127],{"x":123,"y":109,"fill":83,"style":110},"broad support",[86,129,130],{"x":123,"y":114,"fill":83,"style":110},"cheap decode",[76,132],{"x":133,"y":94,"width":95,"height":96,"rx":97,"fill":119,"stroke":119,"style":134},"516","fill-opacity:0.10;stroke-opacity:0.5",[86,136,138],{"x":137,"y":104,"fill":83,"style":105},"626","img: JPEG",[86,140,141],{"x":137,"y":109,"fill":83,"style":110},"universal floor",[86,143,144],{"x":137,"y":114,"fill":83,"style":110},"always renders",[146,147],"line",{"x1":148,"y1":149,"x2":118,"y2":149,"stroke":83,"style":150},"244","97","stroke-opacity:0.5",[146,152],{"x1":153,"y1":149,"x2":133,"y2":149,"stroke":83,"style":150},"490",[146,155],{"x1":88,"y1":156,"x2":157,"y2":156,"stroke":83,"style":158},"162","736","stroke-opacity:0.3",[86,160,163],{"x":88,"y":161,"fill":83,"style":162},"192","font-size:14px;font-weight:600","Browser uses the FIRST source it can decode — order matters.",[86,165,168],{"x":88,"y":166,"fill":83,"style":167},"224","font-size:13px","Put the smallest supported codec first; the img is the guaranteed floor.",[86,170,172],{"x":88,"y":171,"fill":83,"style":167},"252","Each source carries its own srcset, so resolution selection still applies per codec.",[86,174,176],{"x":88,"y":175,"fill":83,"style":167},"288","Byte savings are real; decode time on the main thread is the hidden tax.",[86,178,180],{"x":88,"y":179,"fill":83,"style":167},"316","Validate that the smaller file still paints sooner, not just downloads sooner.",[182,183,185],"h2",{"id":184},"_1-environment-setup-encoders-and-source-assets","1. Environment Setup: Encoders and Source Assets",[15,187,188,189,192,193,196,197,200,201,204,205,208,209,212,213,216],{},"Settle the toolchain before you encode a single file, because the encoder you choose determines both the byte floor and how long your build takes. For AVIF the reference encoder is ",[33,190,191],{},"avifenc"," from libavif (which wraps the AOM ",[33,194,195],{},"aom"," encoder); for WebP it is ",[33,198,199],{},"cwebp"," from libwebp. In a Node build, ",[33,202,203],{},"sharp"," exposes both through one API (",[33,206,207],{},".avif()"," and ",[33,210,211],{},".webp()","), which is the path of least friction for most pipelines; ",[33,214,215],{},"squoosh"," is excellent for interactive, per-image tuning when you are still finding your quality knee but is awkward to run at scale in CI. Pin the encoder versions: AVIF output is not bit-stable across libaom releases, and an unpinned bump can silently change your byte budgets between builds.",[15,218,219,220,54],{},"Start from the highest-fidelity master you have — ideally the original capture or a lossless PNG, never a previously compressed JPEG. Encoding AVIF or WebP from an already-lossy JPEG bakes in the JPEG's block artifacts and then spends bytes preserving them, so you pay twice and still look worse. The master must also be at least as wide as the largest width in your resolution ladder; codec choice does not rescue a pixel deficit, as covered in the source-asset discussion in ",[19,221,44],{"href":43},[15,223,224,225,228,229,231,232,234],{},"The decision that bites teams later is ",[47,226,227],{},"where"," in the pipeline encoding runs. There are three viable positions, each with a distinct operational profile. Build-time encoding (sharp in a CI step, or ",[33,230,191],{},"\u002F",[33,233,199],{}," invoked by your bundler) produces static variants you can fingerprint and cache forever; it is the right home for a fixed set of marketing and product imagery because the encode cost is paid once and the output is fully CDN-cacheable. Request-time encoding behind an image CDN transcodes on demand from a single origin master, which is the only sane option for user-generated content where you cannot enumerate the asset set ahead of time. The hybrid — build static variants for the known critical assets and let the CDN handle everything else — is what most production sites converge on. Whichever you choose, treat the encoder version, the quality settings, and the chroma mode as part of the artifact's cache key, because changing any of them must invalidate the cached output; a silent quality change that reuses the old cached bytes is a class of bug that survives every functional test.",[236,237,242],"pre",{"className":238,"code":239,"language":240,"meta":241,"style":241},"language-bash shiki shiki-themes github-light-high-contrast github-light-high-contrast github-light-high-contrast","# Encode one width into AVIF and WebP from a clean master\navifenc --min 0 --max 63 -a end-usage=q -a cq-level=28 \\\n        --speed 6 hero-master.png hero-1200.avif\ncwebp -q 78 -m 6 hero-master.png -o hero-1200.webp\n# trade-off: --speed 6 \u002F -m 6 are mid-effort. Dropping to avifenc --speed 2\n# roughly triples encode time for ~3-5% more savings — worth it for a static\n# hero rebuilt rarely, wasteful for thousands of user-uploaded images per minute.\n","bash","",[33,243,244,252,290,305,329,335,341],{"__ignoreMap":241},[245,246,248],"span",{"class":146,"line":247},1,[245,249,251],{"class":250},"sIIH1","# Encode one width into AVIF and WebP from a clean master\n",[245,253,255,258,262,265,268,271,274,278,280,283,286],{"class":146,"line":254},2,[245,256,191],{"class":257},"seIZK",[245,259,261],{"class":260},"sf6mN"," --min",[245,263,264],{"class":260}," 0",[245,266,267],{"class":260}," --max",[245,269,270],{"class":260}," 63",[245,272,273],{"class":260}," -a",[245,275,277],{"class":276},"s-_DF"," end-usage=q",[245,279,273],{"class":260},[245,281,282],{"class":276}," cq-level=",[245,284,285],{"class":260},"28",[245,287,289],{"class":288},"sP5qI"," \\\n",[245,291,293,296,299,302],{"class":146,"line":292},3,[245,294,295],{"class":260},"        --speed",[245,297,298],{"class":260}," 6",[245,300,301],{"class":276}," hero-master.png",[245,303,304],{"class":276}," hero-1200.avif\n",[245,306,308,310,313,316,319,321,323,326],{"class":146,"line":307},4,[245,309,199],{"class":257},[245,311,312],{"class":260}," -q",[245,314,315],{"class":260}," 78",[245,317,318],{"class":260}," -m",[245,320,298],{"class":260},[245,322,301],{"class":276},[245,324,325],{"class":260}," -o",[245,327,328],{"class":276}," hero-1200.webp\n",[245,330,332],{"class":146,"line":331},5,[245,333,334],{"class":250},"# trade-off: --speed 6 \u002F -m 6 are mid-effort. Dropping to avifenc --speed 2\n",[245,336,338],{"class":146,"line":337},6,[245,339,340],{"class":250},"# roughly triples encode time for ~3-5% more savings — worth it for a static\n",[245,342,344],{"class":146,"line":343},7,[245,345,346],{"class":250},"# hero rebuilt rarely, wasteful for thousands of user-uploaded images per minute.\n",[182,348,350],{"id":349},"_2-capture-a-byte-and-quality-baseline","2. Capture a Byte and Quality Baseline",[15,352,353],{},"Quantify what each codec actually buys before wiring up the chain. Take one representative master and encode it three ways — JPEG at quality 72, WebP, and AVIF — targeting visually matched quality, then record transferred bytes for each in the DevTools Network panel. Matched quality is the discipline that makes the comparison honest: comparing AVIF at quality 30 against JPEG at quality 90 proves nothing. Use a perceptual metric — SSIMULACRA2 or at minimum a butteraugli\u002FDSSIM score — rather than the encoder's internal quality integer, because the same numeric \"quality\" means different things across codecs.",[15,355,356,357,361,362,365,366,369],{},"Tabulate three columns per format: transferred bytes, the perceptual score, and the ",[358,359,360],"strong",{},"bytes-per-quality-point"," ratio that lets you compare encoders on equal footing. Then add the number that the byte savings can hide: decode time. In the DevTools Performance panel, record a load and find the ",[33,363,364],{},"Decode Image"," (or ",[33,367,368],{},"Image Decode",") task for the hero; AVIF decode is meaningfully heavier than JPEG, and on a low-end phone a large AVIF can spend 30–80ms decoding on the main thread. That decode sits inside your LCP render-delay phase, so a file that downloads 100KB faster but decodes 60ms slower has a smaller net LCP win than the byte chart suggests. Capturing decode alongside bytes is what separates a real improvement from a paper one.",[15,371,372],{},"Run the baseline on more than one image, because the codec ranking is content-dependent and a single sample will mislead you. Pick at least one image from each class you actually ship: a noisy photograph, a smooth-gradient image (a sky, a soft product backdrop), a flat illustration or chart, and a screenshot containing text. Encode each in all three formats at matched perceptual quality and watch how the AVIF lead swings — wide on the noisy photo, narrow or even negative on the flat chart where banding forces the quality back up. This per-class table is the artifact you carry into the rest of the workflow: it tells you which content classes justify AVIF, which are fine on WebP, and which (text-heavy screenshots especially) may belong in a lossless format entirely. Without it you will set one global quality and either under-compress your photos or visibly degrade your charts. Finally, normalize for size in the baseline: record bytes at a fixed display width, not at the master resolution, because a codec that wins at 1600px can lose at 320px once its fixed container overhead dominates the payload.",[182,374,376],{"id":375},"_3-isolate-delivery-type-negotiation-vs-accept-header-negotiation","3. Isolate Delivery: Type Negotiation vs Accept-Header Negotiation",[15,378,379,380,387,388,390,391,208,394,397,398,400],{},"There are two mechanisms for getting the right codec to each client, and they fail in different ways. ",[358,381,382,383,386],{},"Client-side ",[33,384,385],{},"type"," negotiation"," uses ",[33,389,35],{}," with ",[33,392,393],{},"\u003Csource type=\"image\u002Favif\">",[33,395,396],{},"\u003Csource type=\"image\u002Fwebp\">","; the browser walks the sources top to bottom and uses the first whose ",[33,399,385],{}," it can decode. This is fully static, CDN-cacheable under a single URL per resource, and requires no server logic — but it ships the markup for every codec to every client and locks you into emitting all variants at build time.",[15,402,403,409,410,412,413,416,417,421,422,425,426,429,430,433,434,437,438,440,441,443],{},[358,404,405,406,408],{},"Server-side ",[33,407,39],{},"-header negotiation"," inspects the request's ",[33,411,39],{}," header (browsers that support AVIF send ",[33,414,415],{},"image\u002Favif"," in it) and returns the best codec from a single image URL. This keeps markup minimal and lets an ",[19,418,420],{"href":419},"\u002Fimage-media-optimization\u002Fimage-cdns-and-fetchpriority\u002F","image CDN"," transcode on demand, but it makes the response vary by request, so you ",[358,423,424],{},"must"," set ",[33,427,428],{},"Vary: Accept"," or a cache will serve an AVIF body to a client that asked for JPEG. The two approaches are not mutually exclusive: a common production shape is a plain ",[33,431,432],{},"\u003Cimg>"," whose ",[33,435,436],{},"src"," points at a CDN doing ",[33,439,39],{}," negotiation, falling back to client-side ",[33,442,35],{}," only where you need explicit control over the fallback order.",[15,445,446,447,449,450,452,453,455,456,458,459,461,462,465,466,468],{},"The trade-off that decides between them is almost always cache behavior, not markup verbosity. ",[33,448,428],{}," is correct but blunt: because the ",[33,451,39],{}," header that browsers send for images is not perfectly uniform across versions and proxies, a naive ",[33,454,428],{}," can fragment your cache into more variants than the two or three codecs you actually serve, lowering hit rate and pushing more requests to origin. Mature image CDNs sidestep this by normalizing ",[33,457,39],{}," internally to a small set of canonical variants before keying the cache, so confirm your CDN does that rather than varying on the raw header. Client-side ",[33,460,385],{}," negotiation has the opposite profile: one immutable URL per variant, a perfect cache key, and no origin involvement at request time — at the cost of emitting and storing every variant up front and shipping all the ",[33,463,464],{},"\u003Csource>"," markup to every client. There is also a discoverability difference that matters for the LCP image: with static ",[33,467,35],{},", the preload scanner can see the candidate URLs in the HTML and start the fetch immediately, whereas with a single CDN URL the negotiation and any redirect add latency before the bytes flow. For the hero specifically, prefer the path that lets the scanner commit to a concrete, high-priority URL as early as possible.",[236,470,474],{"className":471,"code":472,"language":473,"meta":241,"style":241},"language-nginx shiki shiki-themes github-light-high-contrast github-light-high-contrast github-light-high-contrast","# Edge Accept-header negotiation: serve AVIF only to clients that advertise it\nmap $http_accept $img_ext {\n    default        \"jpg\";\n    \"~*image\u002Favif\" \"avif\";\n    \"~*image\u002Fwebp\" \"webp\";\n}\nlocation ~* ^\u002Fimg\u002F(?\u003Cname>.+)\\.(jpg|jpeg)$ {\n    add_header Vary Accept;                 # REQUIRED or caches cross-serve codecs\n    try_files \u002Fimg\u002F$name.$img_ext \u002Fimg\u002F$name.jpg =404;\n}\n# trade-off: Vary: Accept fragments the CDN cache across Accept variants and the\n# regex match runs per request. For a small fixed asset set, static \u003Cpicture>\n# with build-time variants caches better under one URL and skips the server logic.\n","nginx",[33,475,476,481,496,507,517,527,532,546,558,572,577,583,589],{"__ignoreMap":241},[245,477,478],{"class":146,"line":247},[245,479,480],{"class":250},"# Edge Accept-header negotiation: serve AVIF only to clients that advertise it\n",[245,482,483,486,490,493],{"class":146,"line":254},[245,484,485],{"class":288},"map",[245,487,489],{"class":488},"syybb"," $",[245,491,492],{"class":257},"http_accept",[245,494,495],{"class":488}," $img_ext {\n",[245,497,498,501,504],{"class":146,"line":292},[245,499,500],{"class":260},"    default",[245,502,503],{"class":276},"        \"jpg\"",[245,505,506],{"class":488},";\n",[245,508,509,512,515],{"class":146,"line":307},[245,510,511],{"class":276},"    \"~*image\u002Favif\"",[245,513,514],{"class":276}," \"avif\"",[245,516,506],{"class":488},[245,518,519,522,525],{"class":146,"line":331},[245,520,521],{"class":276},"    \"~*image\u002Fwebp\"",[245,523,524],{"class":276}," \"webp\"",[245,526,506],{"class":488},[245,528,529],{"class":146,"line":337},[245,530,531],{"class":488},"}\n",[245,533,534,537,540,543],{"class":146,"line":343},[245,535,536],{"class":288},"location",[245,538,539],{"class":288}," ~*",[245,541,542],{"class":276}," ^\u002Fimg\u002F(?\u003Cname>.+)\\.(jpg|jpeg)$ ",[245,544,545],{"class":488},"{\n",[245,547,549,552,555],{"class":146,"line":548},8,[245,550,551],{"class":288},"    add_header ",[245,553,554],{"class":488},"Vary Accept;                 ",[245,556,557],{"class":250},"# REQUIRED or caches cross-serve codecs\n",[245,559,561,564,567,570],{"class":146,"line":560},9,[245,562,563],{"class":288},"    try_files ",[245,565,566],{"class":488},"\u002Fimg\u002F$name.$img_ext \u002Fimg\u002F$name.jpg ",[245,568,569],{"class":260},"=404",[245,571,506],{"class":488},[245,573,575],{"class":146,"line":574},10,[245,576,531],{"class":488},[245,578,580],{"class":146,"line":579},11,[245,581,582],{"class":250},"# trade-off: Vary: Accept fragments the CDN cache across Accept variants and the\n",[245,584,586],{"class":146,"line":585},12,[245,587,588],{"class":250},"# regex match runs per request. For a small fixed asset set, static \u003Cpicture>\n",[245,590,592],{"class":146,"line":591},13,[245,593,594],{"class":250},"# with build-time variants caches better under one URL and skips the server logic.\n",[182,596,598],{"id":597},"_4-apply-the-fix-the-picture-type-fallback-chain","4. Apply the Fix: The picture Type Fallback Chain",[15,600,601,602,433,604,606,607,433,609,611,612,614,615,617,618,621,622,231,625,628,629,231,632,635,636,638,639,641,642,208,645,648],{},"For client-side delivery, the corrected markup is a ",[33,603,35],{},[33,605,464],{}," elements are ordered smallest-codec-first. The browser commits to the first ",[33,608,464],{},[33,610,385],{}," it supports, so AVIF precedes WebP, and the inner ",[33,613,432],{}," — carrying the JPEG ",[33,616,436],{},", ",[33,619,620],{},"alt",", intrinsic ",[33,623,624],{},"width",[33,626,627],{},"height",", and ",[33,630,631],{},"decoding",[33,633,634],{},"fetchpriority"," — is the mandatory floor that renders when no ",[33,637,464],{}," matches. Each ",[33,640,464],{}," keeps its own ",[33,643,644],{},"srcset",[33,646,647],{},"sizes",", so resolution selection runs independently per codec; you are composing two orthogonal axes, format and size, in one element.",[15,650,651,652,208,654,656,657,659,660,664,665,668,669,54],{},"Always keep the intrinsic ",[33,653,624],{},[33,655,627],{}," on the ",[33,658,432],{}," to reserve layout space and avoid the ",[19,661,663],{"href":662},"\u002Fcore-web-vitals-measurement\u002Freducing-cumulative-layout-shift-cls\u002F","Cumulative Layout Shift"," that comes from images snapping in after decode. For an LCP hero, add ",[33,666,667],{},"fetchpriority=\"high\""," so the chosen candidate is requested early rather than waiting behind the preload scanner's default ordering — the priority discussion lives in ",[19,670,671],{"href":419},"image CDNs and fetchpriority",[236,673,677],{"className":674,"code":675,"language":676,"meta":241,"style":241},"language-html shiki shiki-themes github-light-high-contrast github-light-high-contrast github-light-high-contrast","\u003C!-- Type fallback chain: AVIF, then WebP, then JPEG floor -->\n\u003Cpicture>\n  \u003Csource type=\"image\u002Favif\"\n          srcset=\"hero-800.avif 800w, hero-1200.avif 1200w, hero-1600.avif 1600w\"\n          sizes=\"(max-width: 600px) 100vw, 1100px\">\n  \u003Csource type=\"image\u002Fwebp\"\n          srcset=\"hero-800.webp 800w, hero-1200.webp 1200w, hero-1600.webp 1600w\"\n          sizes=\"(max-width: 600px) 100vw, 1100px\">\n  \u003Cimg src=\"hero-1200.jpg\" width=\"1200\" height=\"675\"\n       alt=\"Quarterly revenue dashboard\"\n       decoding=\"async\" fetchpriority=\"high\">\n\u003C\u002Fpicture>\n\u003C!-- trade-off: this triples your build outputs and cache entries per image.\n     For below-the-fold or rarely-viewed images, the storage and build cost of\n     three full ladders can outweigh the byte savings — ship AVIF+JPEG only,\n     or skip AVIF and serve WebP+JPEG, where decode budget is tight. -->\n","html",[33,678,679,684,696,713,723,735,748,757,767,797,807,827,836,841,847,853],{"__ignoreMap":241},[245,680,681],{"class":146,"line":247},[245,682,683],{"class":250},"\u003C!-- Type fallback chain: AVIF, then WebP, then JPEG floor -->\n",[245,685,686,689,693],{"class":146,"line":254},[245,687,688],{"class":488},"\u003C",[245,690,692],{"class":691},"s-fAs","picture",[245,694,695],{"class":488},">\n",[245,697,698,701,704,707,710],{"class":146,"line":292},[245,699,700],{"class":488},"  \u003C",[245,702,703],{"class":691},"source",[245,705,706],{"class":260}," type",[245,708,709],{"class":488},"=",[245,711,712],{"class":276},"\"image\u002Favif\"\n",[245,714,715,718,720],{"class":146,"line":307},[245,716,717],{"class":260},"          srcset",[245,719,709],{"class":488},[245,721,722],{"class":276},"\"hero-800.avif 800w, hero-1200.avif 1200w, hero-1600.avif 1600w\"\n",[245,724,725,728,730,733],{"class":146,"line":331},[245,726,727],{"class":260},"          sizes",[245,729,709],{"class":488},[245,731,732],{"class":276},"\"(max-width: 600px) 100vw, 1100px\"",[245,734,695],{"class":488},[245,736,737,739,741,743,745],{"class":146,"line":337},[245,738,700],{"class":488},[245,740,703],{"class":691},[245,742,706],{"class":260},[245,744,709],{"class":488},[245,746,747],{"class":276},"\"image\u002Fwebp\"\n",[245,749,750,752,754],{"class":146,"line":343},[245,751,717],{"class":260},[245,753,709],{"class":488},[245,755,756],{"class":276},"\"hero-800.webp 800w, hero-1200.webp 1200w, hero-1600.webp 1600w\"\n",[245,758,759,761,763,765],{"class":146,"line":548},[245,760,727],{"class":260},[245,762,709],{"class":488},[245,764,732],{"class":276},[245,766,695],{"class":488},[245,768,769,771,773,776,778,781,784,786,789,792,794],{"class":146,"line":560},[245,770,700],{"class":488},[245,772,63],{"class":691},[245,774,775],{"class":260}," src",[245,777,709],{"class":488},[245,779,780],{"class":276},"\"hero-1200.jpg\"",[245,782,783],{"class":260}," width",[245,785,709],{"class":488},[245,787,788],{"class":276},"\"1200\"",[245,790,791],{"class":260}," height",[245,793,709],{"class":488},[245,795,796],{"class":276},"\"675\"\n",[245,798,799,802,804],{"class":146,"line":574},[245,800,801],{"class":260},"       alt",[245,803,709],{"class":488},[245,805,806],{"class":276},"\"Quarterly revenue dashboard\"\n",[245,808,809,812,814,817,820,822,825],{"class":146,"line":579},[245,810,811],{"class":260},"       decoding",[245,813,709],{"class":488},[245,815,816],{"class":276},"\"async\"",[245,818,819],{"class":260}," fetchpriority",[245,821,709],{"class":488},[245,823,824],{"class":276},"\"high\"",[245,826,695],{"class":488},[245,828,829,832,834],{"class":146,"line":585},[245,830,831],{"class":488},"\u003C\u002F",[245,833,692],{"class":691},[245,835,695],{"class":488},[245,837,838],{"class":146,"line":591},[245,839,840],{"class":250},"\u003C!-- trade-off: this triples your build outputs and cache entries per image.\n",[245,842,844],{"class":146,"line":843},14,[245,845,846],{"class":250},"     For below-the-fold or rarely-viewed images, the storage and build cost of\n",[245,848,850],{"class":146,"line":849},15,[245,851,852],{"class":250},"     three full ladders can outweigh the byte savings — ship AVIF+JPEG only,\n",[245,854,856],{"class":146,"line":855},16,[245,857,858],{"class":250},"     or skip AVIF and serve WebP+JPEG, where decode budget is tight. -->\n",[182,860,862],{"id":861},"deconstructing-the-encoder-knobs-quality-effort-and-speed","Deconstructing the Encoder Knobs: Quality, Effort, and Speed",[15,864,865,866,869,870,872,873,876,877,879,880,883,884,887,888,891,892,895,896,899,900,903],{},"Encoder output is governed by two independent dials, and conflating them wastes either bytes or build minutes. ",[358,867,868],{},"Quality"," (the perceptual target) controls how aggressively detail is discarded; in ",[33,871,191],{}," this is ",[33,874,875],{},"cq-level"," (lower is higher quality, ~20–32 is the photographic sweet spot), and in ",[33,878,199],{}," it is ",[33,881,882],{},"-q"," (higher is higher quality, ~72–82 for photos). ",[358,885,886],{},"Effort\u002Fspeed"," controls how hard the encoder searches for a smaller representation ",[47,889,890],{},"at that quality"," — ",[33,893,894],{},"avifenc --speed"," (lower is slower and smaller) and ",[33,897,898],{},"cwebp -m"," (higher is slower and smaller). Effort changes encode ",[47,901,902],{},"time and file size","; it does not change decode time, which is a property of the bitstream and the decoder, not how long you spent encoding.",[15,905,906,907,909,910,912,913,915],{},"The practical consequence is that quality and effort are tuned against different budgets. Quality is tuned against a perceptual floor: encode a ladder of ",[33,908,875],{}," values, score each with SSIMULACRA2, and pick the highest ",[33,911,875],{}," (smallest file) that still clears your perceptual threshold — this is the byte\u002Fquality knee. Effort is tuned against your build budget: maximum effort on a static hero rebuilt weekly costs nothing the user sees, but maximum effort on an upload pipeline serving thousands of images per minute can blow your processing SLA for a few percent of bytes. A defensible default is mid-effort with quality chosen per content class — photographic heroes tolerate more compression than flat illustrations or text-bearing screenshots, which show banding and ringing far earlier and often justify a lower ",[33,914,875],{}," or even staying on WebP\u002FPNG.",[15,917,918,919,922],{},"There is a third axis on AVIF specifically: chroma subsampling. AVIF can encode 4:4:4 (full chroma) or 4:2:0 (subsampled), and the default 4:2:0 is right for photos but smears fine colored detail — red text on a colored background, sharp UI chrome. For those assets, forcing 4:4:4 (",[33,920,921],{},"avifenc --yuv 444",") preserves edges at a byte cost; for everything photographic, leave it at 4:2:0. Treat content class as an input to the encoder config, not a one-size pipeline.",[182,924,926],{"id":925},"advanced-diagnostics-decode-cost-animation-and-format-failure-modes","Advanced Diagnostics: Decode Cost, Animation, and Format Failure Modes",[15,928,929,930,933,934,54],{},"The failure mode that survives a clean byte audit is decode cost on the critical path. AVIF decode is CPU-heavy; on a low-end Android device a full-bleed AVIF hero can add tens of milliseconds of decode to the LCP render-delay phase, and ",[33,931,932],{},"decoding=\"async\""," does not help the LCP element because the browser must decode it to paint it. Profile decode on a throttled CPU (6x slowdown in the Performance panel) before assuming AVIF wins for your largest image — sometimes WebP, with cheaper decode and only slightly more bytes, paints sooner end to end. This is the central trade-off explored in ",[19,935,937],{"href":936},"\u002Fimage-media-optimization\u002Fserving-avif-and-webp-with-fallbacks\u002Favif-vs-webp-which-format-to-serve\u002F","AVIF vs WebP: which format to serve",[15,939,940,941,944,945,948,949,231,951,953],{},"A second, quieter failure mode is the progressive-rendering gap. JPEG's long history gave it progressive scans that paint a blurry-then-sharp preview as bytes arrive, which can make a slow JPEG ",[47,942,943],{},"feel"," faster even when its final paint is later. AVIF and WebP do not stream a progressive preview the same way; a partially downloaded AVIF shows nothing until enough of the bitstream has arrived to decode the first tiles. On a fast connection this is irrelevant, but on the slow links where you most want the byte savings, the perceived loading experience can regress even as the metric improves. The mitigation is a lightweight inline placeholder — a tiny blurred LQIP encoded as a data URI, or a CSS ",[33,946,947],{},"background"," color sampled from the image — so the layout slot is never visually empty while the modern-codec bytes stream in. This composes cleanly with the intrinsic ",[33,950,624],{},[33,952,627],{}," you already set for layout stability: the dimensions reserve the box, the placeholder fills it, and the decoded image replaces it.",[15,955,956],{},"A third failure mode is over-eager AVIF on assets that re-compress poorly downstream. If an image will later be screenshotted, embedded in a PDF export, or re-encoded by an email client, the artifacts that AVIF's aggressive quantization leaves can compound badly through a second lossy pass. For assets with an uncertain downstream life, a slightly larger WebP or a quality-conservative AVIF is the safer call than the smallest possible file. None of these failure modes argue against modern codecs — they argue for choosing the codec and quality per asset's role rather than running one global setting and trusting the byte chart.",[15,958,959,960,963,964,967],{},"Animation and transparency are their own decision points. For animation, neither codec should be your reflex: animated AVIF and animated WebP exist but are heavy to decode and awkward to author; a short, muted, ",[33,961,962],{},"playsinline"," MP4\u002FWebM via ",[33,965,966],{},"\u003Cvideo>"," almost always beats an animated image format on both bytes and decode. For transparency, both AVIF and WebP carry an alpha channel and crush PNG on byte size, so a logo with transparency belongs in WebP or AVIF, not PNG — but watch for halo artifacts on hard alpha edges at aggressive quality. Finally, beware the \"AVIF is always smaller\" reflex on small images: codec container overhead means that below a few KB a WebP or even an optimized PNG can undercut AVIF, so size-gate your codec choice rather than blindly encoding everything to AVIF.",[182,969,971],{"id":970},"validation-and-performance-budgeting","Validation and Performance Budgeting",[15,973,974,975,978,979,981],{},"Validation closes the loop opened by your baseline. Re-run the three-format measurement and confirm the chosen codec both transfers fewer bytes ",[47,976,977],{},"and"," paints no later: capture transferred bytes in the Network panel and the ",[33,980,364],{}," task in the Performance panel, and verify the net effect on the LCP timestamp, not just on download size. The concrete budget: an above-the-fold hero should land well under a hard byte ceiling (e.g. ≤ 100KB for the LCP image) while keeping decode under ~16ms on a mid-tier device so it fits inside a single frame.",[15,983,984,985,988,989,992],{},"Enforce both halves in CI. Lighthouse's ",[33,986,987],{},"modern-image-formats"," audit flags any image still served as legacy JPEG\u002FPNG where a modern codec would save meaningful bytes; assert it at ",[33,990,991],{},"minScore"," 1 so a regression — someone adds an image without the modern variants — fails the build. Pair it with a hard byte budget on the LCP image and an LCP assertion, since a heavy decode can pass a byte budget while still regressing the metric.",[236,994,998],{"className":995,"code":996,"language":997,"meta":241,"style":241},"language-json shiki shiki-themes github-light-high-contrast github-light-high-contrast github-light-high-contrast","{\n  \"ci\": {\n    \"assert\": {\n      \"assertions\": {\n        \"modern-image-formats\": [\"error\", { \"minScore\": 1 }],\n        \"uses-optimized-images\": [\"error\", { \"minScore\": 1 }],\n        \"largest-contentful-paint\": [\"error\", { \"maxNumericValue\": 2500 }]\n      }\n    }\n  }\n}\n","json",[33,999,1000,1004,1012,1019,1026,1051,1070,1092,1097,1102,1107],{"__ignoreMap":241},[245,1001,1002],{"class":146,"line":247},[245,1003,545],{"class":488},[245,1005,1006,1009],{"class":146,"line":254},[245,1007,1008],{"class":691},"  \"ci\"",[245,1010,1011],{"class":488},": {\n",[245,1013,1014,1017],{"class":146,"line":292},[245,1015,1016],{"class":691},"    \"assert\"",[245,1018,1011],{"class":488},[245,1020,1021,1024],{"class":146,"line":307},[245,1022,1023],{"class":691},"      \"assertions\"",[245,1025,1011],{"class":488},[245,1027,1028,1031,1034,1037,1040,1043,1046,1048],{"class":146,"line":331},[245,1029,1030],{"class":691},"        \"modern-image-formats\"",[245,1032,1033],{"class":488},": [",[245,1035,1036],{"class":276},"\"error\"",[245,1038,1039],{"class":488},", { ",[245,1041,1042],{"class":691},"\"minScore\"",[245,1044,1045],{"class":488},": ",[245,1047,78],{"class":260},[245,1049,1050],{"class":488}," }],\n",[245,1052,1053,1056,1058,1060,1062,1064,1066,1068],{"class":146,"line":337},[245,1054,1055],{"class":691},"        \"uses-optimized-images\"",[245,1057,1033],{"class":488},[245,1059,1036],{"class":276},[245,1061,1039],{"class":488},[245,1063,1042],{"class":691},[245,1065,1045],{"class":488},[245,1067,78],{"class":260},[245,1069,1050],{"class":488},[245,1071,1072,1075,1077,1079,1081,1084,1086,1089],{"class":146,"line":343},[245,1073,1074],{"class":691},"        \"largest-contentful-paint\"",[245,1076,1033],{"class":488},[245,1078,1036],{"class":276},[245,1080,1039],{"class":488},[245,1082,1083],{"class":691},"\"maxNumericValue\"",[245,1085,1045],{"class":488},[245,1087,1088],{"class":260},"2500",[245,1090,1091],{"class":488}," }]\n",[245,1093,1094],{"class":146,"line":548},[245,1095,1096],{"class":488},"      }\n",[245,1098,1099],{"class":146,"line":560},[245,1100,1101],{"class":488},"    }\n",[245,1103,1104],{"class":146,"line":574},[245,1105,1106],{"class":488},"  }\n",[245,1108,1109],{"class":146,"line":579},[245,1110,531],{"class":488},[15,1112,1113],{},[47,1114,1115,1116,1118],{},"Use ",[33,1117,987],{}," to catch any asset still shipping as legacy JPEG\u002FPNG before merge.",[182,1120,1122],{"id":1121},"related","Related",[1124,1125,1126,1132,1138,1144,1150],"ul",{},[1127,1128,1129,1131],"li",{},[19,1130,937],{"href":936}," — the decision matrix for picking a primary codec per content class and device tier.",[1127,1133,1134,1137],{},[19,1135,1136],{"href":43},"Responsive images with srcset and sizes"," — the resolution ladder that composes with codec selection inside each source.",[1127,1139,1140,1143],{},[19,1141,1142],{"href":419},"Image CDNs and fetchpriority"," — transcode on demand via Accept negotiation and ensure the chosen file fetches early.",[1127,1145,1146,1149],{},[19,1147,1148],{"href":26},"Measuring LCP with Chrome DevTools"," — confirm the codec win actually moved the loading metric rather than just the download size.",[1127,1151,1152,1154],{},[19,1153,22],{"href":21}," — the broader media-weight strategy this codec workflow plugs into.",[1156,1157,1159],"script",{"type":1158},"application\u002Fld+json","\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"HowTo\",\n  \"name\": \"Serve AVIF and WebP with a fallback chain\",\n  \"description\": \"Encode AVIF and WebP from a clean master, capture a byte and decode baseline, choose type or Accept-header negotiation, build the picture fallback chain, and validate in CI.\",\n  \"step\": [\n    { \"@type\": \"HowToStep\", \"position\": 1, \"name\": \"Set up encoders\", \"text\": \"Pin avifenc, cwebp, or sharp and start from a clean, high-fidelity master rather than a re-encoded JPEG.\", \"url\": \"https:\u002F\u002Ffrontend-performance.com\u002Fimage-media-optimization\u002Fserving-avif-and-webp-with-fallbacks\u002F#1-environment-setup-encoders-and-source-assets\" },\n    { \"@type\": \"HowToStep\", \"position\": 2, \"name\": \"Capture a baseline\", \"text\": \"Encode JPEG, WebP, and AVIF at matched perceptual quality and record bytes plus image decode time.\", \"url\": \"https:\u002F\u002Ffrontend-performance.com\u002Fimage-media-optimization\u002Fserving-avif-and-webp-with-fallbacks\u002F#2-capture-a-byte-and-quality-baseline\" },\n    { \"@type\": \"HowToStep\", \"position\": 3, \"name\": \"Choose a negotiation mechanism\", \"text\": \"Use static picture type negotiation or server-side Accept-header negotiation with Vary: Accept.\", \"url\": \"https:\u002F\u002Ffrontend-performance.com\u002Fimage-media-optimization\u002Fserving-avif-and-webp-with-fallbacks\u002F#3-isolate-delivery-type-negotiation-vs-accept-header-negotiation\" },\n    { \"@type\": \"HowToStep\", \"position\": 4, \"name\": \"Build the fallback chain\", \"text\": \"Order picture sources AVIF then WebP then a JPEG img floor, each with its own srcset and sizes.\", \"url\": \"https:\u002F\u002Ffrontend-performance.com\u002Fimage-media-optimization\u002Fserving-avif-and-webp-with-fallbacks\u002F#4-apply-the-fix-the-picture-type-fallback-chain\" },\n    { \"@type\": \"HowToStep\", \"position\": 5, \"name\": \"Tune quality and effort\", \"text\": \"Set quality against a perceptual floor and effort against the build budget, with chroma per content class.\", \"url\": \"https:\u002F\u002Ffrontend-performance.com\u002Fimage-media-optimization\u002Fserving-avif-and-webp-with-fallbacks\u002F#deconstructing-the-encoder-knobs-quality-effort-and-speed\" },\n    { \"@type\": \"HowToStep\", \"position\": 6, \"name\": \"Validate and budget\", \"text\": \"Confirm the codec paints sooner, then assert modern-image-formats and an LCP budget in CI.\", \"url\": \"https:\u002F\u002Ffrontend-performance.com\u002Fimage-media-optimization\u002Fserving-avif-and-webp-with-fallbacks\u002F#validation-and-performance-budgeting\" }\n  ]\n}\n",[1156,1161,1162],{"type":1158},"\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"TechArticle\",\n  \"headline\": \"Serving AVIF and WebP with Fallbacks: An Encoding and Negotiation Workflow\",\n  \"description\": \"How to encode AVIF and WebP, build the picture type fallback chain, negotiate via the Accept header, tune quality and effort, and weigh decode cost against byte savings.\",\n  \"datePublished\": \"2026-06-18\",\n  \"dateModified\": \"2026-06-18\",\n  \"author\": { \"@type\": \"Organization\", \"name\": \"frontend-performance.com\" },\n  \"publisher\": { \"@type\": \"Organization\", \"name\": \"frontend-performance.com\" },\n  \"mainEntityOfPage\": { \"@type\": \"WebPage\", \"@id\": \"https:\u002F\u002Ffrontend-performance.com\u002Fimage-media-optimization\u002Fserving-avif-and-webp-with-fallbacks\u002F\" }\n}\n",[1156,1164,1165],{"type":1158},"\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"BreadcrumbList\",\n  \"itemListElement\": [\n    { \"@type\": \"ListItem\", \"position\": 1, \"name\": \"Home\", \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002F\" },\n    { \"@type\": \"ListItem\", \"position\": 2, \"name\": \"Image & Media Optimization\", \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fimage-media-optimization\u002F\" },\n    { \"@type\": \"ListItem\", \"position\": 3, \"name\": \"Serving AVIF and WebP with Fallbacks\", \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fimage-media-optimization\u002Fserving-avif-and-webp-with-fallbacks\u002F\" }\n  ]\n}\n",[1167,1168,1169],"style",{},"html pre.shiki code .sIIH1, html code.shiki .sIIH1{--shiki-default:#66707B;--shiki-dark:#66707B;--shiki-light:#66707B}html pre.shiki code .seIZK, html code.shiki .seIZK{--shiki-default:#702C00;--shiki-dark:#702C00;--shiki-light:#702C00}html pre.shiki code .sf6mN, html code.shiki .sf6mN{--shiki-default:#023B95;--shiki-dark:#023B95;--shiki-light:#023B95}html pre.shiki code .s-_DF, html code.shiki .s-_DF{--shiki-default:#032563;--shiki-dark:#032563;--shiki-light:#032563}html pre.shiki code .sP5qI, html code.shiki .sP5qI{--shiki-default:#A0111F;--shiki-dark:#A0111F;--shiki-light:#A0111F}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html pre.shiki code .syybb, html code.shiki .syybb{--shiki-default:#0E1116;--shiki-dark:#0E1116;--shiki-light:#0E1116}html pre.shiki code .s-fAs, html code.shiki .s-fAs{--shiki-default:#024C1A;--shiki-dark:#024C1A;--shiki-light:#024C1A}",{"title":241,"searchDepth":254,"depth":254,"links":1171},[1172,1173,1174,1175,1176,1177,1178,1179],{"id":184,"depth":254,"text":185},{"id":349,"depth":254,"text":350},{"id":375,"depth":254,"text":376},{"id":597,"depth":254,"text":598},{"id":861,"depth":254,"text":862},{"id":925,"depth":254,"text":926},{"id":970,"depth":254,"text":971},{"id":1121,"depth":254,"text":1122},"A production workflow for encoding AVIF and WebP, building the picture type fallback chain, and tuning quality versus decode cost.","md",{"slug":1183,"type":1184,"breadcrumb":1185,"datePublished":1191,"dateModified":1191},"serving-avif-and-webp-with-fallbacks","cluster",[1186,1188,1189],{"name":1187,"url":231},"Home",{"name":22,"url":21},{"name":5,"url":1190},"\u002Fimage-media-optimization\u002Fserving-avif-and-webp-with-fallbacks\u002F","2026-06-18",true,"\u002Fimage-media-optimization\u002Fserving-avif-and-webp-with-fallbacks",{"title":5,"description":1195},"Encode AVIF and WebP, build a picture\u002Fsource type fallback chain, tune quality and effort, and weigh decode cost against byte savings on the LCP image.","image-media-optimization\u002Fserving-avif-and-webp-with-fallbacks\u002Findex","usidyNdjZNc8E5cR1Nrmm3P6dAQ2SktT-qSzIj6CFqw",[1199,1203],{"title":1200,"path":1201,"stem":1202,"children":-1},"Fixing Blurry Images on High-DPI Displays","\u002Fimage-media-optimization\u002Fresponsive-images-with-srcset-and-sizes\u002Ffixing-blurry-images-on-high-dpi-displays","image-media-optimization\u002Fresponsive-images-with-srcset-and-sizes\u002Ffixing-blurry-images-on-high-dpi-displays\u002Findex",{"title":1204,"path":1205,"stem":1206,"children":-1},"AVIF vs WebP: Which Format to Serve","\u002Fimage-media-optimization\u002Fserving-avif-and-webp-with-fallbacks\u002Favif-vs-webp-which-format-to-serve","image-media-optimization\u002Fserving-avif-and-webp-with-fallbacks\u002Favif-vs-webp-which-format-to-serve\u002Findex",1782237170880]