[{"data":1,"prerenderedAt":1435},["ShallowReactive",2],{"content:\u002Fimage-media-optimization\u002Fserving-avif-and-webp-with-fallbacks\u002Fbatch-converting-images-with-sharp":3,"surroundings:\u002Fimage-media-optimization\u002Fserving-avif-and-webp-with-fallbacks\u002Fbatch-converting-images-with-sharp":1426},{"id":4,"title":5,"body":6,"description":1406,"extension":1407,"meta":1408,"navigation":439,"path":1420,"seo":1421,"stem":1424,"__hash__":1425},"content\u002Fimage-media-optimization\u002Fserving-avif-and-webp-with-fallbacks\u002Fbatch-converting-images-with-sharp\u002Findex.md","Batch-Converting Images with sharp",{"type":7,"value":8,"toc":1388},"minimark",[9,14,29,36,190,195,224,228,234,240,246,252,328,332,337,1028,1032,1035,1039,1046,1050,1066,1171,1175,1178,1182,1193,1197,1204,1208,1211,1215,1245,1249,1255,1261,1271,1277,1281,1293,1302,1315,1324,1337,1346,1350,1373,1378,1381,1384],[10,11,13],"h1",{"id":12},"how-to-batch-convert-images-to-avif-and-webp-with-sharp","How to Batch-Convert Images to AVIF and WebP with sharp",[15,16,17,18,23,24,28],"p",{},"This guide is the hands-on companion to ",[19,20,22],"a",{"href":21},"\u002Fimage-media-optimization\u002Fserving-avif-and-webp-with-fallbacks\u002F","Serving AVIF and WebP with Fallbacks",", within ",[19,25,27],{"href":26},"\u002Fimage-media-optimization\u002F","Image & Media Optimization",". Most sites that want modern formats already have hundreds or thousands of JPEGs and PNGs in a repository, a CMS export or object storage. Converting them is mechanical but has traps: running out of memory with large batches, losing colour accuracy when stripping profiles, rotating photos wrongly because EXIF orientation was ignored, regenerating unchanged files on every run, and switching references before every variant exists.",[15,30,31,35],{},[32,33,34],"code",{},"sharp"," — the Node.js binding to libvips — is the standard tool: fast, memory-efficient, and able to resize and encode AVIF, WebP and JPEG in one pipeline. A good conversion script processes images with bounded concurrency, normalises orientation and colour, writes outputs named by content hash, skips work already done, and produces a manifest that templates can use.",[15,37,38],{},[39,40,46,47,46,54,46,58,46,61,46,79,46,87,46,93,46,101,46,108,46,113,46,117,46,120,46,124,46,127,46,130,46,136,46,140,46,143,46,146,46,149,46,153,46,156,46,159,46,163,46,166,46,169,46,178,46,182,46,186,46],"svg",{"viewBox":41,"width":42,"role":43,"ariaLabel":44,"style":45},"0 0 760 149","100%","img","Process from scanning source images through bounded conversion and verification to switching references.","height:auto;max-width:760px;display:block;margin:1.75rem auto;font-family:inherit;color:var(--fp-svg-ink)"," ",[48,49],"rect",{"className":50,"x":52,"y":52,"width":42,"height":42,"fill":53},[51],"svg-canvas","0","#ffffff",[55,56,57],"title",{},"Safe batch conversion process",[59,60,44],"desc",{},[62,63,64],"defs",{},[65,66,73],"marker",{"id":67,"viewBox":68,"refX":69,"refY":70,"markerWidth":71,"markerHeight":71,"orient":72},"fa33c5a0d9","0 0 10 10","9","5","7","auto-start-reverse",[74,75],"path",{"d":76,"fill":77,"style":78},"M0 0 L10 5 L0 10 z","currentColor","fill-opacity:0.7",[48,80],{"x":81,"y":81,"width":82,"height":83,"rx":84,"fill":85,"stroke":77,"style":86},"1","758","147","10","none","stroke-opacity:0.18",[88,89,57],"text",{"x":90,"y":91,"fill":77,"style":92},"28.0","34.0","font-size:16px;font-weight:700",[48,94],{"x":90,"y":95,"width":96,"height":97,"rx":98,"fill":99,"stroke":99,"style":100},"56.0","108.8","71.0","6","#0466c8","fill-opacity:0.14;stroke-opacity:0.9",[88,102,107],{"x":103,"y":104,"fill":77,"style":105,"textAnchor":106},"82.4","78.0","font-size:13px;font-weight:700","middle","Scan",[88,109,112],{"x":103,"y":110,"fill":77,"style":111,"textAnchor":106},"96.0","font-size:12px","list sources +",[88,114,116],{"x":103,"y":115,"fill":77,"style":111,"textAnchor":106},"112.0","hashes",[48,118],{"x":119,"y":95,"width":96,"height":97,"rx":98,"fill":99,"stroke":99,"style":100},"176.8",[88,121,123],{"x":122,"y":104,"fill":77,"style":105,"textAnchor":106},"231.2","Convert",[88,125,126],{"x":122,"y":110,"fill":77,"style":111,"textAnchor":106},"bounded",[88,128,129],{"x":122,"y":115,"fill":77,"style":111,"textAnchor":106},"concurrency",[48,131],{"x":132,"y":95,"width":96,"height":97,"rx":98,"fill":133,"stroke":134,"style":135},"325.6","#ffc300","#b8860b","fill-opacity:0.24;stroke-opacity:0.9",[88,137,139],{"x":138,"y":104,"fill":77,"style":105,"textAnchor":106},"380.0","Verify",[88,141,142],{"x":138,"y":110,"fill":77,"style":111,"textAnchor":106},"counts, sizes,",[88,144,145],{"x":138,"y":115,"fill":77,"style":111,"textAnchor":106},"samples",[48,147],{"x":148,"y":95,"width":96,"height":97,"rx":98,"fill":99,"stroke":99,"style":100},"474.4",[88,150,152],{"x":151,"y":104,"fill":77,"style":105,"textAnchor":106},"528.8","Manifest",[88,154,155],{"x":151,"y":110,"fill":77,"style":111,"textAnchor":106},"dims + URLs",[48,157],{"x":158,"y":95,"width":96,"height":97,"rx":98,"fill":99,"stroke":99,"style":100},"623.2",[88,160,162],{"x":161,"y":104,"fill":77,"style":105,"textAnchor":106},"677.6","Switch refs",[88,164,165],{"x":161,"y":110,"fill":77,"style":111,"textAnchor":106},"templates use",[88,167,168],{"x":161,"y":115,"fill":77,"style":111,"textAnchor":106},"manifest",[170,171],"line",{"x1":172,"y1":173,"x2":174,"y2":173,"stroke":77,"strokeWidth":175,"style":176,"markerEnd":177},"136.8","91.5","174.8","1.5","stroke-opacity:0.6","url(#fa33c5a0d9)",[170,179],{"x1":180,"y1":173,"x2":181,"y2":173,"stroke":77,"strokeWidth":175,"style":176,"markerEnd":177},"285.6","323.6",[170,183],{"x1":184,"y1":173,"x2":185,"y2":173,"stroke":77,"strokeWidth":175,"style":176,"markerEnd":177},"434.4","472.4",[170,187],{"x1":188,"y1":173,"x2":189,"y2":173,"stroke":77,"strokeWidth":175,"style":176,"markerEnd":177},"583.2","621.2",[191,192,194],"h2",{"id":193},"rapid-diagnosis","Rapid Diagnosis",[196,197,198,206,212,218],"ul",{},[199,200,201,205],"li",{},[202,203,204],"strong",{},"Inventory sources."," Count images by format and size; very large originals (over 20MP) need care with memory.",[199,207,208,211],{},[202,209,210],{},"Check orientation handling."," Phone photos often rely on EXIF orientation; outputs must be rotated correctly.",[199,213,214,217],{},[202,215,216],{},"Check colour profiles."," Images in Adobe RGB or Display P3 can shift colour when converted carelessly.",[199,219,220,223],{},[202,221,222],{},"Plan storage."," Widths × formats multiplies files; estimate storage and upload costs.",[191,225,227],{"id":226},"root-cause-analysis-what-goes-wrong-in-naive-conversions","Root Cause Analysis: What Goes Wrong in Naive Conversions",[15,229,230,233],{},[202,231,232],{},"1. Unbounded parallelism."," Launching thousands of conversions at once exhausts memory and crashes.",[15,235,236,239],{},[202,237,238],{},"2. Orientation loss."," Stripping metadata without applying EXIF rotation produces sideways images.",[15,241,242,245],{},[202,243,244],{},"3. Colour shifts."," Dropping embedded profiles without converting to sRGB changes colours.",[15,247,248,251],{},[202,249,250],{},"4. Reprocessing everything."," Without caching, every run re-encodes all images, making AVIF conversion prohibitively slow.",[15,253,254],{},[39,255,46,258,46,261,46,264,46,266,46,269,46,271,46,278,46,285,46,290,46,294,46,299,46,303,46,307,46,311,46,315,46,319,46,324,46],{"viewBox":256,"width":42,"role":43,"ariaLabel":257,"style":45},"0 0 760 194","Bar chart of total storage for an image library before and after conversion to AVIF and WebP variants.",[48,259],{"className":260,"x":52,"y":52,"width":42,"height":42,"fill":53},[51],[55,262,263],{},"Library conversion results (12,000 product images)",[59,265,257],{},[48,267],{"x":81,"y":81,"width":82,"height":268,"rx":84,"fill":85,"stroke":77,"style":86},"192",[88,270,263],{"x":90,"y":91,"fill":77,"style":92},[88,272,277],{"x":273,"y":274,"fill":77,"style":275,"textAnchor":276},"176.3","70.0","font-size:13px","end","Original JPEGs (one size)",[48,279],{"x":280,"y":95,"width":281,"height":282,"rx":283,"fill":133,"stroke":134,"style":284},"188.3","479.7","19","3","fill-opacity:0.7;stroke-opacity:0.9",[88,286,289],{"x":287,"y":274,"fill":77,"style":288},"674.0","font-size:12px;font-weight:600","18.4GB",[88,291,293],{"x":273,"y":292,"fill":77,"style":275,"textAnchor":276},"101.0","AVIF variants (5 widths)",[48,295],{"x":280,"y":296,"width":297,"height":282,"rx":283,"fill":99,"stroke":99,"style":298},"87.0","106.9","fill-opacity:0.55;stroke-opacity:0.9",[88,300,302],{"x":301,"y":292,"fill":77,"style":288},"301.2","4.1GB",[88,304,306],{"x":273,"y":305,"fill":77,"style":275,"textAnchor":276},"132.0","WebP variants (5 widths)",[48,308],{"x":280,"y":309,"width":310,"height":282,"rx":283,"fill":99,"stroke":99,"style":298},"118.0","164.2",[88,312,314],{"x":313,"y":305,"fill":77,"style":288},"358.5","6.3GB",[88,316,318],{"x":273,"y":317,"fill":77,"style":275,"textAnchor":276},"163.0","JPEG fallbacks (5 widths)",[48,320],{"x":280,"y":321,"width":322,"height":282,"rx":283,"fill":77,"stroke":77,"style":323},"149.0","255.5","fill-opacity:0.7;stroke-opacity:0.4",[88,325,327],{"x":326,"y":317,"fill":77,"style":288},"449.8","9.8GB",[191,329,331],{"id":330},"step-by-step-resolution","Step-by-Step Resolution",[333,334,336],"h3",{"id":335},"_1-write-a-bounded-cached-conversion-script","1. Write a bounded, cached conversion script",[338,339,344],"pre",{"className":340,"code":341,"language":342,"meta":343,"style":343},"language-javascript shiki shiki-themes github-light-high-contrast github-dark-high-contrast github-light-high-contrast","\u002F\u002F scripts\u002Fconvert.mjs\nimport sharp from 'sharp';\nimport pLimit from 'p-limit';\nimport { glob } from 'node:fs\u002Fpromises';\nimport { readFile, mkdir, access, writeFile } from 'node:fs\u002Fpromises';\nimport { createHash } from 'node:crypto';\n\nsharp.concurrency(1);                       \u002F\u002F libvips threads per image; parallelism comes from pLimit\nconst limit = pLimit(4);                    \u002F\u002F ~ number of CPU cores\nconst WIDTHS = [480, 800, 1200, 1600];\nconst manifest = {};\n\nasync function convert(src) {\n  const buf = await readFile(src);\n  const hash = createHash('sha256').update(buf).digest('hex').slice(0, 12);\n  const base = sharp(buf, { failOn: 'truncated' }).rotate().toColourspace('srgb');   \u002F\u002F apply EXIF orientation\n  const meta = await base.metadata();\n  const outputs = [];\n  for (const w of WIDTHS.filter((w) => w \u003C= meta.width)) {\n    for (const [ext, fmt] of [['avif', { quality: 55, effort: 4 }], ['webp', { quality: 78 }]]) {\n      const out = `out\u002F${hash}-${w}.${ext}`;\n      try { await access(out); } catch { await base.clone().resize(w).toFormat(ext, fmt).toFile(out); }\n      outputs.push(out);\n    }\n  }\n  manifest[src] = { hash, width: meta.width, height: meta.height, outputs };\n}\nfor await (const f of glob('src\u002Fimages\u002F**\u002F*.{jpg,jpeg,png}')) await limit(() => convert(f));\nawait writeFile('out\u002Fmanifest.json', JSON.stringify(manifest, null, 2));\n\u002F\u002F trade-off: the loop awaits each limit() call, serialising scheduling; for very\n\u002F\u002F large libraries collect promises and await Promise.all in batches instead.\n","javascript","",[32,345,346,354,375,390,405,419,434,441,462,488,523,536,541,562,582,634,676,697,710,756,813,845,895,907,913,919,931,937,979,1016,1022],{"__ignoreMap":343},[347,348,350],"span",{"class":170,"line":349},1,[347,351,353],{"class":352},"sjfSM","\u002F\u002F scripts\u002Fconvert.mjs\n",[347,355,357,361,365,368,372],{"class":170,"line":356},2,[347,358,360],{"class":359},"sPARh","import",[347,362,364],{"class":363},"saISM"," sharp ",[347,366,367],{"class":359},"from",[347,369,371],{"class":370},"sZ8jY"," 'sharp'",[347,373,374],{"class":363},";\n",[347,376,378,380,383,385,388],{"class":170,"line":377},3,[347,379,360],{"class":359},[347,381,382],{"class":363}," pLimit ",[347,384,367],{"class":359},[347,386,387],{"class":370}," 'p-limit'",[347,389,374],{"class":363},[347,391,393,395,398,400,403],{"class":170,"line":392},4,[347,394,360],{"class":359},[347,396,397],{"class":363}," { glob } ",[347,399,367],{"class":359},[347,401,402],{"class":370}," 'node:fs\u002Fpromises'",[347,404,374],{"class":363},[347,406,408,410,413,415,417],{"class":170,"line":407},5,[347,409,360],{"class":359},[347,411,412],{"class":363}," { readFile, mkdir, access, writeFile } ",[347,414,367],{"class":359},[347,416,402],{"class":370},[347,418,374],{"class":363},[347,420,422,424,427,429,432],{"class":170,"line":421},6,[347,423,360],{"class":359},[347,425,426],{"class":363}," { createHash } ",[347,428,367],{"class":359},[347,430,431],{"class":370}," 'node:crypto'",[347,433,374],{"class":363},[347,435,437],{"class":170,"line":436},7,[347,438,440],{"emptyLinePlaceholder":439},true,"\n",[347,442,444,447,450,453,456,459],{"class":170,"line":443},8,[347,445,446],{"class":363},"sharp.",[347,448,129],{"class":449},"smZ65",[347,451,452],{"class":363},"(",[347,454,81],{"class":455},"sPXB4",[347,457,458],{"class":363},");                       ",[347,460,461],{"class":352},"\u002F\u002F libvips threads per image; parallelism comes from pLimit\n",[347,463,465,468,471,474,477,479,482,485],{"class":170,"line":464},9,[347,466,467],{"class":359},"const",[347,469,470],{"class":455}," limit",[347,472,473],{"class":359}," =",[347,475,476],{"class":449}," pLimit",[347,478,452],{"class":363},[347,480,481],{"class":455},"4",[347,483,484],{"class":363},");                    ",[347,486,487],{"class":352},"\u002F\u002F ~ number of CPU cores\n",[347,489,491,493,496,498,501,504,507,510,512,515,517,520],{"class":170,"line":490},10,[347,492,467],{"class":359},[347,494,495],{"class":455}," WIDTHS",[347,497,473],{"class":359},[347,499,500],{"class":363}," [",[347,502,503],{"class":455},"480",[347,505,506],{"class":363},", ",[347,508,509],{"class":455},"800",[347,511,506],{"class":363},[347,513,514],{"class":455},"1200",[347,516,506],{"class":363},[347,518,519],{"class":455},"1600",[347,521,522],{"class":363},"];\n",[347,524,526,528,531,533],{"class":170,"line":525},11,[347,527,467],{"class":359},[347,529,530],{"class":455}," manifest",[347,532,473],{"class":359},[347,534,535],{"class":363}," {};\n",[347,537,539],{"class":170,"line":538},12,[347,540,440],{"emptyLinePlaceholder":439},[347,542,544,547,550,553,555,559],{"class":170,"line":543},13,[347,545,546],{"class":359},"async",[347,548,549],{"class":359}," function",[347,551,552],{"class":449}," convert",[347,554,452],{"class":363},[347,556,558],{"class":557},"sQw3B","src",[347,560,561],{"class":363},") {\n",[347,563,565,568,571,573,576,579],{"class":170,"line":564},14,[347,566,567],{"class":359},"  const",[347,569,570],{"class":455}," buf",[347,572,473],{"class":359},[347,574,575],{"class":359}," await",[347,577,578],{"class":449}," readFile",[347,580,581],{"class":363},"(src);\n",[347,583,585,587,590,592,595,597,600,603,606,609,612,614,617,619,622,624,626,628,631],{"class":170,"line":584},15,[347,586,567],{"class":359},[347,588,589],{"class":455}," hash",[347,591,473],{"class":359},[347,593,594],{"class":449}," createHash",[347,596,452],{"class":363},[347,598,599],{"class":370},"'sha256'",[347,601,602],{"class":363},").",[347,604,605],{"class":449},"update",[347,607,608],{"class":363},"(buf).",[347,610,611],{"class":449},"digest",[347,613,452],{"class":363},[347,615,616],{"class":370},"'hex'",[347,618,602],{"class":363},[347,620,621],{"class":449},"slice",[347,623,452],{"class":363},[347,625,52],{"class":455},[347,627,506],{"class":363},[347,629,630],{"class":455},"12",[347,632,633],{"class":363},");\n",[347,635,637,639,642,644,647,650,653,656,659,662,665,667,670,673],{"class":170,"line":636},16,[347,638,567],{"class":359},[347,640,641],{"class":455}," base",[347,643,473],{"class":359},[347,645,646],{"class":449}," sharp",[347,648,649],{"class":363},"(buf, { failOn: ",[347,651,652],{"class":370},"'truncated'",[347,654,655],{"class":363}," }).",[347,657,658],{"class":449},"rotate",[347,660,661],{"class":363},"().",[347,663,664],{"class":449},"toColourspace",[347,666,452],{"class":363},[347,668,669],{"class":370},"'srgb'",[347,671,672],{"class":363},");   ",[347,674,675],{"class":352},"\u002F\u002F apply EXIF orientation\n",[347,677,679,681,684,686,688,691,694],{"class":170,"line":678},17,[347,680,567],{"class":359},[347,682,683],{"class":455}," meta",[347,685,473],{"class":359},[347,687,575],{"class":359},[347,689,690],{"class":363}," base.",[347,692,693],{"class":449},"metadata",[347,695,696],{"class":363},"();\n",[347,698,700,702,705,707],{"class":170,"line":699},18,[347,701,567],{"class":359},[347,703,704],{"class":455}," outputs",[347,706,473],{"class":359},[347,708,709],{"class":363}," [];\n",[347,711,713,716,719,721,724,727,729,732,735,738,741,744,747,750,753],{"class":170,"line":712},19,[347,714,715],{"class":359},"  for",[347,717,718],{"class":363}," (",[347,720,467],{"class":359},[347,722,723],{"class":455}," w",[347,725,726],{"class":359}," of",[347,728,495],{"class":455},[347,730,731],{"class":363},".",[347,733,734],{"class":449},"filter",[347,736,737],{"class":363},"((",[347,739,740],{"class":557},"w",[347,742,743],{"class":363},") ",[347,745,746],{"class":359},"=>",[347,748,749],{"class":363}," w ",[347,751,752],{"class":359},"\u003C=",[347,754,755],{"class":363}," meta.width)) {\n",[347,757,759,762,764,766,768,771,773,776,779,782,785,788,791,794,797,799,802,805,807,810],{"class":170,"line":758},20,[347,760,761],{"class":359},"    for",[347,763,718],{"class":363},[347,765,467],{"class":359},[347,767,500],{"class":363},[347,769,770],{"class":455},"ext",[347,772,506],{"class":363},[347,774,775],{"class":455},"fmt",[347,777,778],{"class":363},"] ",[347,780,781],{"class":359},"of",[347,783,784],{"class":363}," [[",[347,786,787],{"class":370},"'avif'",[347,789,790],{"class":363},", { quality: ",[347,792,793],{"class":455},"55",[347,795,796],{"class":363},", effort: ",[347,798,481],{"class":455},[347,800,801],{"class":363}," }], [",[347,803,804],{"class":370},"'webp'",[347,806,790],{"class":363},[347,808,809],{"class":455},"78",[347,811,812],{"class":363}," }]]) {\n",[347,814,816,819,822,824,827,830,833,835,838,840,843],{"class":170,"line":815},21,[347,817,818],{"class":359},"      const",[347,820,821],{"class":455}," out",[347,823,473],{"class":359},[347,825,826],{"class":370}," `out\u002F${",[347,828,829],{"class":363},"hash",[347,831,832],{"class":370},"}-${",[347,834,740],{"class":363},[347,836,837],{"class":370},"}.${",[347,839,770],{"class":363},[347,841,842],{"class":370},"}`",[347,844,374],{"class":363},[347,846,848,851,854,857,860,863,866,868,870,872,875,877,880,883,886,889,892],{"class":170,"line":847},22,[347,849,850],{"class":359},"      try",[347,852,853],{"class":363}," { ",[347,855,856],{"class":359},"await",[347,858,859],{"class":449}," access",[347,861,862],{"class":363},"(out); } ",[347,864,865],{"class":359},"catch",[347,867,853],{"class":363},[347,869,856],{"class":359},[347,871,690],{"class":363},[347,873,874],{"class":449},"clone",[347,876,661],{"class":363},[347,878,879],{"class":449},"resize",[347,881,882],{"class":363},"(w).",[347,884,885],{"class":449},"toFormat",[347,887,888],{"class":363},"(ext, fmt).",[347,890,891],{"class":449},"toFile",[347,893,894],{"class":363},"(out); }\n",[347,896,898,901,904],{"class":170,"line":897},23,[347,899,900],{"class":363},"      outputs.",[347,902,903],{"class":449},"push",[347,905,906],{"class":363},"(out);\n",[347,908,910],{"class":170,"line":909},24,[347,911,912],{"class":363},"    }\n",[347,914,916],{"class":170,"line":915},25,[347,917,918],{"class":363},"  }\n",[347,920,922,925,928],{"class":170,"line":921},26,[347,923,924],{"class":363},"  manifest[src] ",[347,926,927],{"class":359},"=",[347,929,930],{"class":363}," { hash, width: meta.width, height: meta.height, outputs };\n",[347,932,934],{"class":170,"line":933},27,[347,935,936],{"class":363},"}\n",[347,938,940,943,945,947,949,952,954,957,959,962,965,967,969,972,974,976],{"class":170,"line":939},28,[347,941,942],{"class":359},"for",[347,944,575],{"class":359},[347,946,718],{"class":363},[347,948,467],{"class":359},[347,950,951],{"class":455}," f",[347,953,726],{"class":359},[347,955,956],{"class":449}," glob",[347,958,452],{"class":363},[347,960,961],{"class":370},"'src\u002Fimages\u002F**\u002F*.{jpg,jpeg,png}'",[347,963,964],{"class":363},")) ",[347,966,856],{"class":359},[347,968,470],{"class":449},[347,970,971],{"class":363},"(() ",[347,973,746],{"class":359},[347,975,552],{"class":449},[347,977,978],{"class":363},"(f));\n",[347,980,982,984,987,989,992,994,997,999,1002,1005,1008,1010,1013],{"class":170,"line":981},29,[347,983,856],{"class":359},[347,985,986],{"class":449}," writeFile",[347,988,452],{"class":363},[347,990,991],{"class":370},"'out\u002Fmanifest.json'",[347,993,506],{"class":363},[347,995,996],{"class":455},"JSON",[347,998,731],{"class":363},[347,1000,1001],{"class":449},"stringify",[347,1003,1004],{"class":363},"(manifest, ",[347,1006,1007],{"class":455},"null",[347,1009,506],{"class":363},[347,1011,1012],{"class":455},"2",[347,1014,1015],{"class":363},"));\n",[347,1017,1019],{"class":170,"line":1018},30,[347,1020,1021],{"class":352},"\u002F\u002F trade-off: the loop awaits each limit() call, serialising scheduling; for very\n",[347,1023,1025],{"class":170,"line":1024},31,[347,1026,1027],{"class":352},"\u002F\u002F large libraries collect promises and await Promise.all in batches instead.\n",[333,1029,1031],{"id":1030},"_2-verify-before-switching-references","2. Verify before switching references",[15,1033,1034],{},"Check that every source has the expected outputs, compare total sizes, and visually spot-check a random sample (including portrait phone photos and images with embedded profiles).",[333,1036,1038],{"id":1037},"_3-switch-references-through-the-manifest","3. Switch references through the manifest",[15,1040,1041,1042,1045],{},"Templates read the manifest to emit ",[32,1043,1044],{},"\u003Cpicture>"," markup; images missing from the manifest keep their original references. Never rename or delete originals until all references have moved.",[333,1047,1049],{"id":1048},"_4-upload-with-immutable-caching","4. Upload with immutable caching",[15,1051,1052,1053,1056,1057,718,1060,506,1063,602],{},"Upload outputs with ",[32,1054,1055],{},"Cache-Control: public, max-age=31536000, immutable"," and correct ",[32,1058,1059],{},"Content-Type",[32,1061,1062],{},"image\u002Favif",[32,1064,1065],{},"image\u002Fwebp",[15,1067,1068],{},[39,1069,46,1072,46,1075,46,1078,46,1080,46,1083,46,1085,46,1090,46,1096,46,1100,46,1103,46,1107,46,1111,46,1114,46,1118,46,1122,46,1125,46,1129,46,1133,46,1139,46,1143,46,1147,46,1152,46,1154,46,1157,46,1159,46,1162,46,1165,46,1168,46],{"viewBox":1070,"width":42,"role":43,"ariaLabel":1071,"style":45},"0 0 760 318","Checks that make a batch image conversion safe to roll out.",[48,1073],{"className":1074,"x":52,"y":52,"width":42,"height":42,"fill":53},[51],[55,1076,1077],{},"Conversion safety checklist",[59,1079,1071],{},[48,1081],{"x":81,"y":81,"width":82,"height":1082,"rx":84,"fill":85,"stroke":77,"style":86},"316",[88,1084,1077],{"x":90,"y":91,"fill":77,"style":92},[48,1086],{"x":1087,"y":95,"width":1088,"height":1089,"rx":98,"fill":99,"stroke":99,"style":100},"72.0","660.0","51.0",[88,1091,1095],{"x":1092,"y":1093,"fill":77,"style":105,"textAnchor":1094},"86.0","77.0","start","Bounded concurrency",[88,1097,1099],{"x":1092,"y":1098,"fill":77,"style":111,"textAnchor":1094},"94.0","Avoid memory exhaustion on large libraries",[48,1101],{"x":1087,"y":1102,"width":1088,"height":1089,"rx":98,"fill":99,"stroke":99,"style":100},"119.0",[88,1104,1106],{"x":1092,"y":1105,"fill":77,"style":105,"textAnchor":1094},"140.0","rotate() and sRGB conversion",[88,1108,1110],{"x":1092,"y":1109,"fill":77,"style":111,"textAnchor":1094},"157.0","Correct orientation and colour",[48,1112],{"x":1087,"y":1113,"width":1088,"height":1089,"rx":98,"fill":99,"stroke":99,"style":100},"182.0",[88,1115,1117],{"x":1092,"y":1116,"fill":77,"style":105,"textAnchor":1094},"203.0","Content-hash names and skip-if-exists",[88,1119,1121],{"x":1092,"y":1120,"fill":77,"style":111,"textAnchor":1094},"220.0","Fast re-runs; immutable caching",[48,1123],{"x":1087,"y":1124,"width":1088,"height":1089,"rx":98,"fill":99,"stroke":99,"style":100},"245.0",[88,1126,1128],{"x":1092,"y":1127,"fill":77,"style":105,"textAnchor":1094},"266.0","Verify then switch via manifest",[88,1130,1132],{"x":1092,"y":1131,"fill":77,"style":111,"textAnchor":1094},"283.0","No broken references mid-rollout",[170,1134],{"x1":1135,"y1":1136,"x2":1135,"y2":1137,"stroke":77,"strokeWidth":175,"style":1138},"43.0","95.5","130.5","stroke-opacity:0.3",[170,1140],{"x1":1135,"y1":1141,"x2":1135,"y2":1142,"stroke":77,"strokeWidth":175,"style":1138},"158.5","193.5",[170,1144],{"x1":1135,"y1":1145,"x2":1135,"y2":1146,"stroke":77,"strokeWidth":175,"style":1138},"221.5","256.5",[1148,1149],"circle",{"cx":1135,"cy":1150,"r":1151,"fill":99},"81.5","13",[88,1153,81],{"x":1135,"y":1092,"fill":53,"style":105,"textAnchor":106},[1148,1155],{"cx":1135,"cy":1156,"r":1151,"fill":99},"144.5",[88,1158,1012],{"x":1135,"y":321,"fill":53,"style":105,"textAnchor":106},[1148,1160],{"cx":1135,"cy":1161,"r":1151,"fill":99},"207.5",[88,1163,283],{"x":1135,"y":1164,"fill":53,"style":105,"textAnchor":106},"212.0",[1148,1166],{"cx":1135,"cy":1167,"r":1151,"fill":99},"270.5",[88,1169,481],{"x":1135,"y":1170,"fill":53,"style":105,"textAnchor":106},"275.0",[191,1172,1174],{"id":1173},"verification","Verification",[15,1176,1177],{},"Run the script twice: the second run should finish in seconds (everything cached). Compare a sample of outputs side by side with originals at 100% on a high-DPR display. After switching references, crawl key templates and check for 404s on image URLs, then compare image bytes per page view in RUM.",[191,1179,1181],{"id":1180},"worked-example-a-marketplace-catalogue","Worked Example: A Marketplace Catalogue",[15,1183,1184,1185,1188,1189,1192],{},"A marketplace converted 12,000 product images (18GB of single-size JPEGs). A first naive script with ",[32,1186,1187],{},"Promise.all"," over all files crashed after a few hundred images. The bounded script above (four concurrent images on an 8-core runner) converted the library in about 70 minutes; re-runs on new uploads took seconds. Sixty phone photos had relied on EXIF orientation — ",[32,1190,1191],{},"rotate()"," fixed them automatically. After switching templates to the manifest, average product page image weight dropped by 72%, and LCP p75 on mobile improved by 650ms.",[191,1194,1196],{"id":1195},"converting-at-the-edge-instead","Converting at the Edge Instead",[15,1198,1199,1200,731],{},"For very large or constantly changing libraries (user uploads, marketplaces with millions of images), batch conversion may be impractical. Image CDNs and self-hosted proxies such as imgproxy convert on first request and cache the result, removing the batch step entirely. A hybrid is common: batch-convert the editorial and site images you control, and let an image proxy handle user content. See ",[19,1201,1203],{"href":1202},"\u002Fimage-media-optimization\u002Fimage-cdns-and-fetchpriority\u002Fself-hosting-an-image-proxy-with-imgproxy\u002F","self-hosting an image proxy with imgproxy",[191,1205,1207],{"id":1206},"running-the-conversion-incrementally","Running the Conversion Incrementally",[15,1209,1210],{},"Large libraries rarely convert cleanly in one pass. Plan for interruptions: because outputs are named by content hash and skipped when they already exist, a crashed or cancelled run can simply be restarted and continues where it stopped. Write failures to a separate log (corrupt files, unsupported colour spaces, images over the pixel limit) rather than aborting the batch, then fix those files by hand. Roll references out section by section — one category or template at a time — and watch error rates and image 404s before moving on. Keep the manifest under version control or in object storage with history, so a bad batch can be rolled back by restoring the previous manifest rather than deleting files. Finally, wire the same script into the upload path or a nightly job, so new images are converted as they arrive and the library never drifts back to unoptimised originals.",[191,1212,1214],{"id":1213},"common-mistakes","Common Mistakes",[196,1216,1217,1225,1233,1239],{},[199,1218,1219,1224],{},[202,1220,1221,1222,731],{},"Unbounded ",[32,1223,1187],{}," Memory blow-ups on big libraries.",[199,1226,1227,1232],{},[202,1228,1229,1230,731],{},"Forgetting ",[32,1231,1191],{}," Sideways photos.",[199,1234,1235,1238],{},[202,1236,1237],{},"Stripping profiles without converting."," Colour shifts.",[199,1240,1241,1244],{},[202,1242,1243],{},"Switching references before conversion completes."," Broken images.",[191,1246,1248],{"id":1247},"edge-cases","Edge Cases",[15,1250,1251,1254],{},[202,1252,1253],{},"Animated GIFs."," Handle separately (convert to video or animated formats); still-image settings will flatten them.",[15,1256,1257,1260],{},[202,1258,1259],{},"Transparent PNGs."," Keep alpha in AVIF\u002FWebP; use PNG, not JPEG, for fallbacks.",[15,1262,1263,1266,1267,1270],{},[202,1264,1265],{},"Huge source images."," Set ",[32,1268,1269],{},"limitInputPixels"," appropriately; extremely large images may need more memory or pre-scaling.",[15,1272,1273,1276],{},[202,1274,1275],{},"HEIC sources."," sharp's HEIC support depends on build options; convert HEIC uploads server-side with a supported toolchain.",[191,1278,1280],{"id":1279},"faq","FAQ",[1282,1283,1286,1290],"details",{"className":1284},[1285],"faq-item",[1287,1288,1289],"summary",{},"How fast is sharp for AVIF?",[15,1291,1292],{},"Much slower than JPEG or WebP — often hundreds of milliseconds to seconds per large image depending on effort. Caching by content hash and parallelism across cores make large conversions practical.",[1282,1294,1296,1299],{"className":1295},[1285],[1287,1297,1298],{},"Should I keep the originals?",[15,1300,1301],{},"Yes. Keep originals as the source of truth so you can regenerate with new settings or formats later.",[1282,1303,1305,1308],{"className":1304},[1285],[1287,1306,1307],{},"Does sharp preserve metadata?",[15,1309,1310,1311,1314],{},"By default it strips most metadata, which saves bytes. Use ",[32,1312,1313],{},"withMetadata()"," (or newer per-field options) if you must keep copyright or colour profile data.",[1282,1316,1318,1321],{"className":1317},[1285],[1287,1319,1320],{},"Can I run this in CI?",[15,1322,1323],{},"Yes, with output caching between runs (object storage or a CI cache keyed by content hashes). Without caching, AVIF conversion of a large library on every build is too slow.",[1282,1325,1327,1330],{"className":1326},[1285],[1287,1328,1329],{},"What concurrency should I use?",[15,1331,1332,1333,1336],{},"Roughly the number of CPU cores, with ",[32,1334,1335],{},"sharp.concurrency(1)"," so libvips does not also multithread each image. Monitor memory for very large sources.",[1282,1338,1340,1343],{"className":1339},[1285],[1287,1341,1342],{},"How do I handle images referenced in Markdown or a CMS?",[15,1344,1345],{},"Rewrite references at render time using the manifest (a Markdown plugin or CMS field formatter), so content authors keep referencing originals and templates emit optimised markup.",[191,1347,1349],{"id":1348},"related","Related",[196,1351,1352,1359,1366],{},[199,1353,1354,1358],{},[19,1355,1357],{"href":1356},"\u002Fimage-media-optimization\u002Fserving-avif-and-webp-with-fallbacks\u002Fchoosing-avif-and-webp-quality-settings\u002F","Choosing AVIF and WebP quality settings"," — the settings this script uses.",[199,1360,1361,1365],{},[19,1362,1364],{"href":1363},"\u002Fimage-media-optimization\u002Fresponsive-images-with-srcset-and-sizes\u002Fgenerating-responsive-images-at-build-time\u002F","Generating responsive images at build time"," — integrating conversion into builds.",[199,1367,1368,1372],{},[19,1369,1371],{"href":1370},"\u002Fimage-media-optimization\u002Fvideo-performance-optimization\u002Freplacing-animated-gifs-with-video\u002F","Replacing animated GIFs with video"," — the animated-image path.",[1374,1375,1377],"script",{"type":1376},"application\u002Fld+json","\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"HowTo\",\n  \"name\": \"How to Batch-Convert Images to AVIF and WebP with sharp\",\n  \"description\": \"A practical, production-safe script and process for converting an existing image library to modern formats with sharp.\",\n  \"step\": [\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 1,\n      \"name\": \"Write a bounded, cached conversion script\",\n      \"text\": \"sharp.concurrency(1); \u002F\u002F libvips threads per image; parallelism comes from pLimit const limit = pLimit(4); \u002F\u002F ~ number of CPU cores const WIDTHS = [480, 800, 1200, 1600]; const manifest = {};\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 2,\n      \"name\": \"Verify before switching references\",\n      \"text\": \"Check that every source has the expected outputs, compare total sizes, and visually spot-check a random sample (including portrait phone photos and images with embedded profiles).\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 3,\n      \"name\": \"Switch references through the manifest\",\n      \"text\": \"Templates read the manifest to emit markup; images missing from the manifest keep their original references.\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 4,\n      \"name\": \"Upload with immutable caching\",\n      \"text\": \"Upload outputs with Cache-Control: public, max-age=31536000, immutable and correct Content-Type (image\u002Favif, image\u002Fwebp).\"\n    }\n  ]\n}\n",[1374,1379,1380],{"type":1376},"\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"TechArticle\",\n  \"headline\": \"How to Batch-Convert Images to AVIF and WebP with sharp\",\n  \"description\": \"A practical, production-safe script and process for converting an existing image library to modern formats with sharp.\",\n  \"datePublished\": \"2026-10-06\",\n  \"dateModified\": \"2026-10-06\",\n  \"author\": {\n    \"@type\": \"Organization\",\n    \"name\": \"frontend-performance.com\"\n  },\n  \"publisher\": {\n    \"@type\": \"Organization\",\n    \"name\": \"frontend-performance.com\"\n  },\n  \"mainEntityOfPage\": {\n    \"@type\": \"WebPage\",\n    \"@id\": \"https:\u002F\u002Ffrontend-performance.com\u002Fimage-media-optimization\u002Fserving-avif-and-webp-with-fallbacks\u002Fbatch-converting-images-with-sharp\u002F\"\n  }\n}\n",[1374,1382,1383],{"type":1376},"\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"BreadcrumbList\",\n  \"itemListElement\": [\n    {\n      \"@type\": \"ListItem\",\n      \"position\": 1,\n      \"name\": \"Home\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002F\"\n    },\n    {\n      \"@type\": \"ListItem\",\n      \"position\": 2,\n      \"name\": \"Image & Media Optimization\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fimage-media-optimization\u002F\"\n    },\n    {\n      \"@type\": \"ListItem\",\n      \"position\": 3,\n      \"name\": \"Serving AVIF and WebP with Fallbacks\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fimage-media-optimization\u002Fserving-avif-and-webp-with-fallbacks\u002F\"\n    },\n    {\n      \"@type\": \"ListItem\",\n      \"position\": 4,\n      \"name\": \"Batch-Converting Images with sharp\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fimage-media-optimization\u002Fserving-avif-and-webp-with-fallbacks\u002Fbatch-converting-images-with-sharp\u002F\"\n    }\n  ]\n}\n",[1385,1386,1387],"style",{},"html pre.shiki code .sjfSM, html code.shiki .sjfSM{--shiki-default:#66707B;--shiki-dark:#BDC4CC;--shiki-light:#66707B}html pre.shiki code .sPARh, html code.shiki .sPARh{--shiki-default:#A0111F;--shiki-dark:#FF9492;--shiki-light:#A0111F}html pre.shiki code .saISM, html code.shiki .saISM{--shiki-default:#0E1116;--shiki-dark:#F0F3F6;--shiki-light:#0E1116}html pre.shiki code .sZ8jY, html code.shiki .sZ8jY{--shiki-default:#032563;--shiki-dark:#ADDCFF;--shiki-light:#032563}html pre.shiki code .smZ65, html code.shiki .smZ65{--shiki-default:#622CBC;--shiki-dark:#DBB7FF;--shiki-light:#622CBC}html pre.shiki code .sPXB4, html code.shiki .sPXB4{--shiki-default:#023B95;--shiki-dark:#91CBFF;--shiki-light:#023B95}html pre.shiki code .sQw3B, html code.shiki .sQw3B{--shiki-default:#702C00;--shiki-dark:#FFB757;--shiki-light:#702C00}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);}",{"title":343,"searchDepth":356,"depth":356,"links":1389},[1390,1391,1392,1398,1399,1400,1401,1402,1403,1404,1405],{"id":193,"depth":356,"text":194},{"id":226,"depth":356,"text":227},{"id":330,"depth":356,"text":331,"children":1393},[1394,1395,1396,1397],{"id":335,"depth":377,"text":336},{"id":1030,"depth":377,"text":1031},{"id":1037,"depth":377,"text":1038},{"id":1048,"depth":377,"text":1049},{"id":1173,"depth":356,"text":1174},{"id":1180,"depth":356,"text":1181},{"id":1195,"depth":356,"text":1196},{"id":1206,"depth":356,"text":1207},{"id":1213,"depth":356,"text":1214},{"id":1247,"depth":356,"text":1248},{"id":1279,"depth":356,"text":1280},{"id":1348,"depth":356,"text":1349},"A practical, production-safe script and process for converting an existing image library to modern formats with sharp.","md",{"slug":1409,"type":1410,"breadcrumb":1411,"datePublished":1419,"dateModified":1419},"batch-converting-images-with-sharp","article",[1412,1415,1416,1417],{"name":1413,"url":1414},"Home","\u002F",{"name":27,"url":26},{"name":22,"url":21},{"name":5,"url":1418},"\u002Fimage-media-optimization\u002Fserving-avif-and-webp-with-fallbacks\u002Fbatch-converting-images-with-sharp\u002F","2026-10-06","\u002Fimage-media-optimization\u002Fserving-avif-and-webp-with-fallbacks\u002Fbatch-converting-images-with-sharp",{"title":1422,"description":1423},"Batch-Converting Images to AVIF and WebP with sharp","Convert image libraries to AVIF and WebP with sharp in Node: concurrency, metadata handling, colour profiles, caching, and safe rollout without broken references.","image-media-optimization\u002Fserving-avif-and-webp-with-fallbacks\u002Fbatch-converting-images-with-sharp\u002Findex","m9DyZSGM9WjHvfzjhTIFpulhr7bLdBiqen2UlPKRm7c",[1427,1431],{"title":1428,"path":1429,"stem":1430},"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",{"title":1432,"path":1433,"stem":1434},"Choosing AVIF and WebP Quality Settings","\u002Fimage-media-optimization\u002Fserving-avif-and-webp-with-fallbacks\u002Fchoosing-avif-and-webp-quality-settings","image-media-optimization\u002Fserving-avif-and-webp-with-fallbacks\u002Fchoosing-avif-and-webp-quality-settings\u002Findex",1791308075822]