Repository: vercel/next.js
Pull request: fix(next/image): use `image-size` pkg as additional format detector

In a previous PR https://github.com/vercel/next.js/pull/82118 we added a fallback to `sharp().metadata()` when the magic number didn't detect the image format in order to improve detection.

But `sharp` can be slow to read metadata, so we should avoid it if we can. 

So this PR uses the existing `image-size` package to detect the content type of the image using pure JS code and only then does it fallback to the native `sharp` code if necessary.

No tests were added since this doesn't change the behavior, only performance.


Changed files:
filename: packages/next/src/compiled/image-detector/detector.js
patch: @@ -0,0 +1 @@
+(()=>{"use strict";var t={436:(t,e,n)=>{Object.defineProperty(e,"__esModule",{value:true});e.BMP=void 0;const r=n(779);e.BMP={validate:t=>(0,r.toUTF8String)(t,0,2)==="BM",calculate:t=>({height:Math.abs((0,r.readInt32LE)(t,22)),width:(0,r.readUInt32LE)(t,18)})}},67:(t,e,n)=>{Object.defineProperty(e,"__esModule",{value:true});e.CUR=void 0;const r=n(845);const i=n(779);const o=2;e.CUR={validate(t){const e=(0,i.readUInt16LE)(t,0);const n=(0,i.readUInt16LE)(t,4);if(e!==0||n===0)return false;const r=(0,i.readUInt16LE)(t,2);return r===o},calculate:t=>r.ICO.calculate(t)}},400:(t,e,n)=>{Object.defineProperty(e,"__esModule",{value:true});e.DDS=void 0;const r=n(779);e.DDS={validate:t=>(0,r.readUInt32LE)(t,0)===542327876,calculate:t=>({height:(0,r.readUInt32LE)(t,12),width:(0,r.readUInt32LE)(t,16)})}},197:(t,e,n)=>{Object.defineProperty(e,"__esModule",{value:true});e.GIF=void 0;const r=n(779);const i=/^GIF8[79]a/;e.GIF={validate:t=>i.test((0,r.toUTF8String)(t,0,6)),calculate:t=>({height:(0,r.readUInt16LE)(t,8),width:(0,r.readUInt16LE)(t,6)})}},974:(t,e,n)=>{Object.defineProperty(e,"__esModule",{value:true});e.HEIF=void 0;const r=n(779);const i={avif:"avif",mif1:"heif",msf1:"heif",heic:"heic",heix:"heic",hevc:"heic",hevx:"heic"};e.HEIF={validate(t){const e=(0,r.toUTF8String)(t,4,8);if(e!=="ftyp")return false;const n=(0,r.findBox)(t,"ftyp",0);if(!n)return false;const o=(0,r.toUTF8String)(t,n.offset+8,n.offset+12);return o in i},calculate(t){const e=(0,r.findBox)(t,"meta",0);const n=e&&(0,r.findBox)(t,"iprp",e.offset+12);const i=n&&(0,r.findBox)(t,"ipco",n.offset+8);const o=i&&(0,r.findBox)(t,"ispe",i.offset+8);if(o){return{height:(0,r.readUInt32BE)(t,o.offset+16),width:(0,r.readUInt32BE)(t,o.offset+12),type:(0,r.toUTF8String)(t,8,12)}}throw new TypeError("Invalid HEIF, no size found")}}},512:(t,e,n)=>{Object.defineProperty(e,"__esModule",{value:true});e.ICNS=void 0;const r=n(779);const i=4+4;const o=4;const s=4;const c={ICON:32,"ICN#":32,"icm#":16,icm4:16,icm8:16,"ics#":16,ics4:16,ics8:16,is32:16,s8mk:16,icp4:16,icl4:32,icl8:32,il32:32,l8mk:32,icp5:32,ic11:32,ich4:48,ich8:48,ih32:48,h8mk:48,icp6:64,ic12:32,it32:128,t8mk:128,ic07:128,ic08:256,ic13:256,ic09:512,ic14:512,ic10:1024};function readImageHeader(t,e){const n=e+s;return[(0,r.toUTF8String)(t,e,n),(0,r.readUInt32BE)(t,n)]}function getImageSize(t){const e=c[t];return{width:e,height:e,type:t}}e.ICNS={validate:t=>(0,r.toUTF8String)(t,0,4)==="icns",calculate(t){const e=t.length;const n=(0,r.readUInt32BE)(t,o);let s=i;let c=readImageHeader(t,s);let a=getImageSize(c[0]);s+=c[1];if(s===n)return a;const d={height:a.height,images:[a],width:a.width};while(s<n&&s<e){c=readImageHeader(t,s);a=getImageSize(c[0]);s+=c[1];d.images.push(a)}return d}}},845:(t,e,n)=>{Object.defineProperty(e,"__esModule",{value:true});e.ICO=void 0;const r=n(779);const i=1;const o=2+2+2;const s=1+1+1+1+2+2+4+4;function getSizeFromOffset(t,e){const n=t[e];return n===0?256:n}function getImageSize(t,e){const n=o+e*s;return{height:getSizeFromOffset(t,n+1),width:getSizeFromOffset(t,n)}}e.ICO={validate(t){const e=(0,r.readUInt16LE)(t,0);const n=(0,r.readUInt16LE)(t,4);if(e!==0||n===0)return false;const o=(0,r.readUInt16LE)(t,2);return o===i},calculate(t){const e=(0,r.readUInt16LE)(t,4);const n=getImageSize(t,0);if(e===1)return n;const i=[n];for(let n=1;n<e;n+=1){i.push(getImageSize(t,n))}return{height:n.height,images:i,width:n.width}}}},229:(t,e,n)=>{Object.defineProperty(e,"__esModule",{value:true});e.typeHandlers=void 0;const r=n(436);const i=n(67);const o=n(400);const s=n(197);const c=n(974);const a=n(512);const d=n(845);const f=n(885);const u=n(530);const l=n(934);const h=n(245);const g=n(33);const I=n(105);const p=n(210);const U=n(448);const w=n(136);const v=n(414);const E=n(753);const m=n(940);const B=n(226);e.typeHandlers={bmp:r.BMP,cur:i.CUR,dds:o.DDS,gif:s.GIF,heif:c.HEIF,icns:a.ICNS,ico:d.ICO,j2c:f.J2C,jp2:u.JP2,jpg:l.JPG,jxl:h.JXL,"jxl-stream":g.JXLStream,ktx:I.KTX,png:p.PNG,pnm:U.PNM,psd:w.PSD,svg:v.SVG,tga:E.TGA,tiff:m.TIFF,webp:B.WEBP}},885:(t,e,n)=>{Object.defineProperty(e,"__esModule",{value:true});e.J2C=void 0;const r=n(779);e.J2C={validate:t=>(0,r.readUInt32BE)(t,0)===4283432785,calculate:t=>({height:(0,r.readUInt32BE)(t,12),width:(0,r.readUInt32BE)(t,8)})}},530:(t,e,n)=>{Object.defineProperty(e,"__esModule",{value:true});e.JP2=void 0;const r=n(779);e.JP2={validate(t){const e=(0,r.toUTF8String)(t,4,8);if(e!=="jP  ")return false;const n=(0,r.findBox)(t,"ftyp",0);if(!n)return false;const i=(0,r.toUTF8String)(t,n.offset+8,n.offset+12);return i==="jp2 "},calculate(t){const e=(0,r.findBox)(t,"jp2h",0);const n=e&&(0,r.findBox)(t,"ihdr",e.offset+8);if(n){return{height:(0,r.readUInt32BE)(t,n.offset+8),width:(0,r.readUInt32BE)(t,n.offset+12)}}throw new TypeError("Unsupported JPEG 2000 format")}}},934:(t,e,n)=>{Object.defineProperty(e,"__esModule",{value:true});e.JPG=void 0;const r=n(779);const i="45786966";const o=2;const s=6;const c=2;const a="4d4d";const d="4949";const f=12;const u=2;function isEXIF(t){return(0,r.toHexString)(t,2,6)===i}function extractSize(t,e){return{height:(0,r.readUInt16BE)(t,e),width:(0,r.readUInt16BE)(t,e+2)}}function extractOrientation(t,e){const n=8;const i=s+n;const o=(0,r.readUInt)(t,16,i,e);for(let n=0;n<o;n++){const o=i+u+n*f;const s=o+f;if(o>t.length){return}const c=t.slice(o,s);const a=(0,r.readUInt)(c,16,0,e);if(a===274){const t=(0,r.readUInt)(c,16,2,e);if(t!==3){return}const n=(0,r.readUInt)(c,32,4,e);if(n!==1){return}return(0,r.readUInt)(c,16,8,e)}}}function validateExifBlock(t,e){const n=t.slice(o,e);const i=(0,r.toHexString)(n,s,s+c);const f=i===a;const u=i===d;if(f||u){return extractOrientation(n,f)}}function validateInput(t,e){if(e>t.length){throw new TypeError("Corrupt JPG, exceeded buffer limits")}}e.JPG={validate:t=>(0,r.toHexString)(t,0,2)==="ffd8",calculate(t){t=t.slice(4);let e;let n;while(t.length){const i=(0,r.readUInt16BE)(t,0);if(t[i]!==255){t=t.slice(1);continue}if(isEXIF(t)){e=validateExifBlock(t,i)}validateInput(t,i);n=t[i+1];if(n===192||n===193||n===194){const n=extractSize(t,i+5);if(!e){return n}return{height:n.height,orientation:e,width:n.width}}t=t.slice(i+2)}throw new TypeError("Invalid JPG, no size found")}}},33:(t,e,n)=>{Object.defineProperty(e,"__esModule",{value:true});e.JXLStream=void 0;const r=n(779);const i=n(112);function calculateImageDimension(t,e){if(e){return 8*(1+t.getBits(5))}else{const e=t.getBits(2);const n=[9,13,18,30][e];return 1+t.getBits(n)}}function calculateImageWidth(t,e,n,r){if(e&&n===0){return 8*(1+t.getBits(5))}else if(n===0){return calculateImageDimension(t,false)}else{const t=[1,1.2,4/3,1.5,16/9,5/4,2];return Math.floor(r*t[n-1])}}e.JXLStream={validate:t=>(0,r.toHexString)(t,0,2)==="ff0a",calculate(t){const e=new i.BitReader(t,"little-endian");const n=e.getBits(1)===1;const r=calculateImageDimension(e,n);const o=e.getBits(3);const s=calculateImageWidth(e,n,o,r);return{width:s,height:r}}}},245:(t,e,n)=>{Object.defineProperty(e,"__esModule",{value:true});e.JXL=void 0;const r=n(779);const i=n(33);function extractCodestream(t){const e=(0,r.findBox)(t,"jxlc",0);if(e){return t.slice(e.offset+8,e.offset+e.size)}const n=extractPartialStreams(t);if(n.length>0){return concatenateCodestreams(n)}return undefined}function extractPartialStreams(t){const e=[];let n=0;while(n<t.length){const i=(0,r.findBox)(t,"jxlp",n);if(!i)break;e.push(t.slice(i.offset+12,i.offset+i.size));n=i.offset+i.size}return e}function concatenateCodestreams(t){const e=t.reduce(((t,e)=>t+e.length),0);const n=new Uint8Array(e);let r=0;for(const e of t){n.set(e,r);r+=e.length}return n}e.JXL={validate:t=>{const e=(0,r.toUTF8String)(t,4,8);if(e!=="JXL ")return false;const n=(0,r.findBox)(t,"ftyp",0);if(!n)return false;const i=(0,r.toUTF8String)(t,n.offset+8,n.offset+12);return i==="jxl "},calculate(t){const e=extractCodestream(t);if(e)return i.JXLStream.calculate(e);throw new Error("No codestream found in JXL container")}}},105:(t,e,n)=>{Object.defineProperty(e,"__esModule",{value:true});e.KTX=void 0;const r=n(779);e.KTX={validate:t=>{const e=(0,r.toUTF8String)(t,1,7);return["KTX 11","KTX 20"].includes(e)},calculate:t=>{const e=t[5]===49?"ktx":"ktx2";const n=e==="ktx"?36:20;return{height:(0,r.readUInt32LE)(t,n+4),width:(0,r.readUInt32LE)(t,n),type:e}}}},210:(t,e,n)=>{Object.defineProperty(e,"__esModule",{value:true});e.PNG=void 0;const r=n(779);const i="PNG\r\n\n";const o="IHDR";const s="CgBI";e.PNG={validate(t){if(i===(0,r.toUTF8String)(t,1,8)){let e=(0,r.toUTF8String)(t,12,16);if(e===s){e=(0,r.toUTF8String)(t,28,32)}if(e!==o){throw new TypeError("Invalid PNG")}return true}return false},calculate(t){if((0,r.toUTF8String)(t,12,16)===s){return{height:(0,r.readUInt32BE)(t,36),width:(0,r.readUInt32BE)(t,32)}}return{height:(0,r.readUInt32BE)(t,20),width:(0,r.readUInt32BE)(t,16)}}}},448:(t,e,n)=>{Object.defineProperty(e,"__esModule",{value:true});e.PNM=void 0;const r=n(779);const i={P1:"pbm/ascii",P2:"pgm/ascii",P3:"ppm/ascii",P4:"pbm",P5:"pgm",P6:"ppm",P7:"pam",PF:"pfm"};const o={default:t=>{let e=[];while(t.length>0){const n=t.shift();if(n[0]==="#"){continue}e=n.split(" ");break}if(e.length===2){return{height:parseInt(e[1],10),width:parseInt(e[0],10)}}else{throw new TypeError("Invalid PNM")}},pam:t=>{const e={};while(t.length>0){const n=t.shift();if(n.length>16||n.charCodeAt(0)>128){continue}const[r,i]=n.split(" ");if(r&&i){e[r.toLowerCase()]=parseInt(i,10)}if(e.height&&e.width){break}}if(e.height&&e.width){return{height:e.height,width:e.width}}else{throw new TypeError("Invalid PAM")}}};e.PNM={validate:t=>(0,r.toUTF8String)(t,0,2)in i,calculate(t){const e=(0,r.toUTF8String)(t,0,2);const n=i[e];const s=(0,r.toUTF8String)(t,3).split(/[\r\n]+/);const c=o[n]||o.default;return c(s)}}},136:(t,e,n)=>{Object.defineProperty(e,"__esModule",{value:true});e.PSD=void 0;const r=n(779);e.PSD={validate:t=>(0,r.toUTF8String)(t,0,4)==="8BPS",calculate:t=>({height:(0,r.readUInt32BE)(t,14),width:(0,r.readUInt32BE)(t,18)})}},414:(t,e,n)=>{Object.defineProperty(e,"__esModule",{value:true});e.SVG=void 0;const r=n(779);const i=/<svg\s([^>"']|"[^"]*"|'[^']*')*>/;const o={height:/\sheight=(['"])([^%]+?)\1/,root:i,viewbox:/\sviewBox=(['"])(.+?)\1/i,width:/\swidth=(['"])([^%]+?)\1/};const s=2.54;const c={in:96,cm:96/s,em:16,ex:8,m:96/s*100,mm:96/s/10,pc:96/72/12,pt:96/72,px:1};const a=new RegExp(`^([0-9.]+(?:e\\d+)?)(${Object.keys(c).join("|")})?$`);function parseLength(t){const e=a.exec(t);if(!e){return undefined}return Math.round(Number(e[1])*(c[e[2]]||1))}function parseViewbox(t){const e=t.split(" ");return{height:parseLength(e[3]),width:parseLength(e[2])}}function parseAttributes(t){const e=t.match(o.width);const n=t.match(o.height);const r=t.match(o.viewbox);return{height:n&&parseLength(n[2]),viewbox:r&&parseViewbox(r[2]),width:e&&parseLength(e[2])}}function calculateByDimensions(t){return{height:t.height,width:t.width}}function calculateByViewbox(t,e){const n=e.width/e.height;if(t.width){return{height:Math.floor(t.width/n),width:t.width}}if(t.height){return{height:t.height,width:Math.floor(t.height*n)}}return{height:e.height,width:e.width}}e.SVG={validate:t=>i.test((0,r.toUTF8String)(t,0,1e3)),calculate(t){const e=(0,r.toUTF8String)(t).match(o.root);if(e){const t=parseAttributes(e[0]);if(t.width&&t.height){return calculateByDimensions(t)}if(t.viewbox){return calculateByViewbox(t,t.viewbox)}}throw new TypeError("Invalid SVG")}}},753:(t,e,n)=>{Object.defineProperty(e,"__esModule",{value:true});e.TGA=void 0;const r=n(779);e.TGA={validate(t){return(0,r.readUInt16LE)(t,0)===0&&(0,r.readUInt16LE)(t,4)===0},calculate(t){return{height:(0,r.readUInt16LE)(t,14),width:(0,r.readUInt16LE)(t,12)}}}},940:(t,e,n)=>{Object.defineProperty(e,"__esModule",{value:true});e.TIFF=void 0;const r=n(147);const i=n(779);function readIFD(t,e,n){const o=(0,i.readUInt)(t,32,4,n);let s=1024;const c=r.statSync(e).size;if(o+s>c){s=c-o-10}const a=new Uint8Array(s);const d=r.openSync(e,"r");r.readSync(d,a,0,s,o);r.closeSync(d);return a.slice(2)}function readValue(t,e){const n=(0,i.readUInt)(t,16,8,e);const r=(0,i.readUInt)(t,16,10,e);return(r<<16)+n}function nextTag(t){if(t.length>24){return t.slice(12)}}function extractTags(t,e){const n={};let r=t;while(r&&r.length){const t=(0,i.readUInt)(r,16,0,e);const o=(0,i.readUInt)(r,16,2,e);const s=(0,i.readUInt)(r,32,4,e);if(t===0){break}else{if(s===1&&(o===3||o===4)){n[t]=readValue(r,e)}r=nextTag(r)}}return n}function determineEndianness(t){const e=(0,i.toUTF8String)(t,0,2);if("II"===e){return"LE"}else if("MM"===e){return"BE"}}const o=["49492a00","4d4d002a"];e.TIFF={validate:t=>o.includes((0,i.toHexString)(t,0,4)),calculate(t,e){if(!e){throw new TypeError("Tiff doesn't support buffer")}const n=determineEndianness(t)==="BE";const r=readIFD(t,e,n);const i=extractTags(r,n);const o=i[256];const s=i[257];if(!o||!s){throw new TypeError("Invalid Tiff. Missing tags")}return{height:s,width:o}}}},779:(t,e)=>{Object.defineProperty(e,"__esModule",{value:true});e.findBox=e.readUInt=e.readUInt32LE=e.readUInt32BE=e.readInt32LE=e.readUInt24LE=e.readUInt16LE=e.readUInt16BE=e.readInt16LE=e.toHexString=e.toUTF8String=void 0;const n=new TextDecoder;const toUTF8String=(t,e=0,r=t.length)=>n.decode(t.slice(e,r));e.toUTF8String=toUTF8String;const toHexString=(t,e=0,n=t.length)=>t.slice(e,n).reduce(((t,e)=>t+("0"+e.toString(16)).slice(-2)),"");e.toHexString=toHexString;const readInt16LE=(t,e=0)=>{const n=t[e]+t[e+1]*2**8;return n|(n&2**15)*131070};e.readInt16LE=readInt16LE;const readUInt16BE=(t,e=0)=>t[e]*2**8+t[e+1];e.readUInt16BE=readUInt16BE;const readUInt16LE=(t,e=0)=>t[e]+t[e+1]*2**8;e.readUInt16LE=readUInt16LE;const readUInt24LE=(t,e=0)=>t[e]+t[e+1]*2**8+t[e+2]*2**16;e.readUInt24LE=readUInt24LE;const readInt32LE=(t,e=0)=>t[e]+t[e+1]*2**8+t[e+2]*2**16+(t[e+3]<<24);e.readInt32LE=readInt32LE;const readUInt32BE=(t,e=0)=>t[e]*2**24+t[e+1]*2**16+t[e+2]*2**8+t[e+3];e.readUInt32BE=readUInt32BE;const readUInt32LE=(t,e=0)=>t[e]+t[e+1]*2**8+t[e+2]*2**16+t[e+3]*2**24;e.readUInt32LE=readUInt32LE;const r={readUInt16BE:e.readUInt16BE,readUInt16LE:e.readUInt16LE,readUInt32BE:e.readUInt32BE,readUInt32LE:e.readUInt32LE};function readUInt(t,e,n,i){n=n||0;const o=i?"BE":"LE";const s="readUInt"+e+o;return r[s](t,n)}e.readUInt=readUInt;function readBox(t,n){if(t.length-n<4)return;const r=(0,e.readUInt32BE)(t,n);if(t.length-n<r)return;return{name:(0,e.toUTF8String)(t,4+n,8+n),offset:n,size:r}}function findBox(t,e,n){while(n<t.length){const r=readBox(t,n);if(!r)break;if(r.name===e)return r;n+=r.size>0?r.size:8}}e.findBox=findBox},226:(t,e,n)=>{Object.defineProperty(e,"__esModule",{value:true});e.WEBP=void 0;const r=n(779);function calculateExtended(t){return{height:1+(0,r.readUInt24LE)(t,7),width:1+(0,r.readUInt24LE)(t,4)}}function calculateLossless(t){return{height:1+((t[4]&15)<<10|t[3]<<2|(t[2]&192)>>6),width:1+((t[2]&63)<<8|t[1])}}function calculateLossy(t){return{height:(0,r.readInt16LE)(t,8)&16383,width:(0,r.readInt16LE)(t,6)&16383}}e.WEBP={validate(t){const e="RIFF"===(0,r.toUTF8String)(t,0,4);const n="WEBP"===(0,r.toUTF8String)(t,8,12);const i="VP8"===(0,r.toUTF8String)(t,12,15);return e&&n&&i},calculate(t){const e=(0,r.toUTF8String)(t,12,16);t=t.slice(20,30);if(e==="VP8X"){const e=t[0];const n=(e&192)===0;const r=(e&1)===0;if(n&&r){return calculateExtended(t)}else{throw new TypeError("Invalid WebP")}}if(e==="VP8 "&&t[0]!==47){return calculateLossy(t)}const n=(0,r.toHexString)(t,3,6);if(e==="VP8L"&&n!=="9d012a"){return calculateLossless(t)}throw new TypeError("Invalid WebP")}}},112:(t,e)=>{Object.defineProperty(e,"__esModule",{value:true});e.BitReader=void 0;class BitReader{constructor(t,e){this.input=t;this.endianness=e;this.byteOffset=2;this.bitOffset=0}getBits(t=1){let e=0;let n=0;while(n<t){if(this.byteOffset>=this.input.length){throw new Error("Reached end of input")}const r=this.input[this.byteOffset];const i=8-this.bitOffset;const o=Math.min(t-n,i);if(this.endianness==="little-endian"){const t=(1<<o)-1;const i=r>>this.bitOffset&t;e|=i<<n}else{const t=(1<<o)-1<<8-this.bitOffset-o;const n=(r&t)>>8-this.bitOffset-o;e=e<<o|n}n+=o;this.bitOffset+=o;if(this.bitOffset===8){this.byteOffset++;this.bitOffset=0}}return e}}e.BitReader=BitReader},147:t=>{t.exports=require("fs")}};var e={};function __nccwpck_require__(n){var r=e[n];if(r!==undefined){return r.exports}var i=e[n]={exports:{}};var o=true;try{t[n](i,i.exports,__nccwpck_require__);o=false}finally{if(o)delete e[n]}return i.exports}if(typeof __nccwpck_require__!=="undefined")__nccwpck_require__.ab=__dirname+"/";var n={};(()=>{var t=n;Object.defineProperty(t,"__esModule",{value:true});t.detector=void 0;const e=__nccwpck_require__(229);const r=Object.keys(e.typeHandlers);const i={56:"psd",66:"bmp",68:"dds",71:"gif",73:"tiff",77:"tiff",82:"webp",105:"icns",137:"png",255:"jpg"};function detector(t){const n=t[0];if(n in i){const r=i[n];if(r&&e.typeHandlers[r].validate(t)){return r}}const finder=n=>e.typeHandlers[n].validate(t);return r.find(finder)}t.detector=detector})();module.exports=n})();
\ No newline at end of file

filename: packages/next/src/server/image-optimizer.ts
patch: @@ -4,6 +4,7 @@ import type { IncomingMessage, ServerResponse } from 'http'
 import { mediaType } from 'next/dist/compiled/@hapi/accept'
 import contentDisposition from 'next/dist/compiled/content-disposition'
 import imageSizeOf from 'next/dist/compiled/image-size'
+import { detector } from 'next/dist/compiled/image-detector/detector.js'
 import isAnimated from 'next/dist/compiled/is-animated'
 import { join } from 'path'
 import nodeUrl, { type UrlWithParsedQuery } from 'url'
@@ -232,11 +233,21 @@ export async function detectContentType(
     return JP2
   }
 
-  const sharp = getSharp(null)
-  const meta = await sharp(buffer)
-    .metadata()
-    .catch((_) => null)
-  switch (meta?.format) {
+  let format:
+    | import('sharp').Metadata['format']
+    | ReturnType<typeof detector>
+    | undefined
+  format = detector(buffer)
+
+  if (!format) {
+    const sharp = getSharp(null)
+    const meta = await sharp(buffer)
+      .metadata()
+      .catch((_) => null)
+    format = meta?.format
+  }
+
+  switch (format) {
     case 'avif':
       return AVIF
     case 'webp':
@@ -251,6 +262,7 @@ export async function detectContentType(
     case 'svg':
       return SVG
     case 'jxl':
+    case 'jxl-stream':
       return JXL
     case 'jp2':
       return JP2
@@ -259,6 +271,12 @@ export async function detectContentType(
       return TIFF
     case 'pdf':
       return PDF
+    case 'bmp':
+      return BMP
+    case 'ico':
+      return ICO
+    case 'icns':
+      return ICNS
     case 'dcraw':
     case 'dz':
     case 'exr':
@@ -271,6 +289,13 @@ export async function detectContentType(
     case 'rad':
     case 'raw':
     case 'v':
+    case 'cur':
+    case 'dds':
+    case 'j2c':
+    case 'ktx':
+    case 'pnm':
+    case 'psd':
+    case 'tga':
     case undefined:
     default:
       return null

filename: packages/next/taskfile.js
patch: @@ -660,6 +660,17 @@ export async function ncc_image_size(task, opts) {
     .target('src/compiled/image-size')
 }
 
+// eslint-disable-next-line camelcase
+externals['image-detector'] = 'next/dist/compiled/image-detector'
+export async function ncc_image_detector(task, opts) {
+  // NOTE: remove this special compile step if the upstream PR lands
+  // https://github.com/image-size/image-size/pull/451
+  await task
+    .source(relative(__dirname, require.resolve('image-size/dist/detector.js')))
+    .ncc({ packageName: 'image-size', externals })
+    .target('src/compiled/image-detector')
+}
+
 // eslint-disable-next-line camelcase
 externals['@hapi/accept'] = 'next/dist/compiled/@hapi/accept'
 export async function ncc_hapi_accept(task, opts) {
@@ -2281,6 +2292,7 @@ export async function ncc(task, opts) {
         'ncc_p_queue',
         'ncc_raw_body',
         'ncc_image_size',
+        'ncc_image_detector',
         'ncc_hapi_accept',
         'ncc_commander',
         'ncc_node_anser',

filename: packages/next/types/$$compiled.internal.d.ts
patch: @@ -464,6 +464,33 @@ declare module 'next/dist/compiled/image-size' {
   export = m
 }
 
+declare module 'next/dist/compiled/image-detector/detector.js' {
+  export function detector(
+    arr: Uint8Array
+  ):
+    | 'bmp'
+    | 'cur'
+    | 'dds'
+    | 'gif'
+    | 'heif'
+    | 'icns'
+    | 'ico'
+    | 'j2c'
+    | 'jp2'
+    | 'jpg'
+    | 'jxl'
+    | 'jxl-stream'
+    | 'ktx'
+    | 'png'
+    | 'pnm'
+    | 'psd'
+    | 'svg'
+    | 'tga'
+    | 'tiff'
+    | 'webp'
+    | undefined
+}
+
 declare module 'next/dist/compiled/@hapi/accept' {
   import m from '@hapi/accept'
   export = m