[{"data":1,"prerenderedAt":1518},["ShallowReactive",2],{"blog-post-/blog/fix-video-blob-previews-ios-react-nextjs/":3},{"id":4,"title":5,"body":6,"description":1492,"extension":1493,"meta":1494,"navigation":90,"path":1513,"seo":1514,"sitemap":1515,"stem":1516,"__hash__":1517},"content/blog/fix-video-blob-previews-ios-react-nextjs.md","How to Fix Video Blob Previews on iOS in React and Next.js",{"type":7,"value":8,"toc":1479},"minimark",[9,13,21,24,32,35,40,43,120,123,136,139,144,147,162,174,178,181,192,195,199,205,210,221,225,228,989,993,1000,1207,1213,1284,1288,1291,1379,1383,1386,1400,1403,1407,1410,1421,1424,1427,1431,1472,1475],[10,11,5],"h1",{"id":12},"how-to-fix-video-blob-previews-on-ios-in-react-and-nextjs",[14,15,16],"p",{},[17,18],"img",{"alt":19,"src":20},"Video blob iOS fix cover","/images/uploads/cover.webp",[14,22,23],{},"Are video previews not working on iOS when users upload a video in your web app?",[14,25,26,27,31],{},"You're not alone. This is a common issue developers face — especially when dealing with ",[28,29,30],"code",{},"blob:"," URLs for client-side video previews in React or Next.js.",[14,33,34],{},"In this blog post, I'll walk you through a real-world issue I encountered in a Next.js project where video previews failed on iOS Safari — and how I fixed it using a custom poster image generator.",[36,37,39],"h2",{"id":38},"the-problem-video-blob-preview-fails-on-ios-safari","❌ The Problem: Video Blob Preview Fails on iOS Safari",[14,41,42],{},"In most modern browsers, previewing a video before uploading it is straightforward:",[44,45,50],"pre",{"className":46,"code":47,"language":48,"meta":49,"style":49},"language-javascript shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","const previewUrl = URL.createObjectURL(file);\n\n\u003Cvideo src={previewUrl} controls />;\n","javascript","",[28,51,52,85,92],{"__ignoreMap":49},[53,54,57,61,65,69,72,75,79,82],"span",{"class":55,"line":56},"line",1,[53,58,60],{"class":59},"spNyl","const",[53,62,64],{"class":63},"sTEyZ"," previewUrl ",[53,66,68],{"class":67},"sMK4o","=",[53,70,71],{"class":63}," URL",[53,73,74],{"class":67},".",[53,76,78],{"class":77},"s2Zo4","createObjectURL",[53,80,81],{"class":63},"(file)",[53,83,84],{"class":67},";\n",[53,86,88],{"class":55,"line":87},2,[53,89,91],{"emptyLinePlaceholder":90},true,"\n",[53,93,95,98,102,105,108,111,114,117],{"class":55,"line":94},3,[53,96,97],{"class":67},"\u003C",[53,99,101],{"class":100},"swJcz","video",[53,103,104],{"class":59}," src",[53,106,107],{"class":67},"={",[53,109,110],{"class":63},"previewUrl",[53,112,113],{"class":67},"} ",[53,115,116],{"class":59},"controls",[53,118,119],{"class":67}," />;\n",[14,121,122],{},"This approach works in:",[124,125,126,130,133],"ul",{},[127,128,129],"li",{},"🟢 Chrome",[127,131,132],{},"🟢 Firefox",[127,134,135],{},"🟢 Android browsers",[14,137,138],{},"But fails in:",[124,140,141],{},[127,142,143],{},"🔴 iOS Safari",[14,145,146],{},"You might notice:",[124,148,149,156,159],{},[127,150,151,152,155],{},"The ",[28,153,154],{},"\u003Cvideo>"," tag remains blank or doesn't load.",[127,157,158],{},"The first frame doesn't render.",[127,160,161],{},"Metadata like duration never loads.",[14,163,164,165,169,170,173],{},"💡 ",[166,167,168],"strong",{},"Important:"," This is not a styling issue (e.g., ",[28,171,172],{},"className",", size, or layout). It's a platform-specific limitation related to how iOS handles Blob URLs, video metadata, and memory.",[36,175,177],{"id":176},"root-cause-ios-handling-of-blob-urls-in-video-elements","Root Cause: iOS Handling of Blob URLs in Video Elements",[14,179,180],{},"iOS Safari has several security and performance restrictions that can cause Blob video previews to fail:",[124,182,183,186,189],{},[127,184,185],{},"Autoplay restrictions even with muted videos",[127,187,188],{},"Limited support for rendering metadata from blob sources",[127,190,191],{},"Inconsistent behavior when seeking in blob-loaded videos",[14,193,194],{},"As a result, you can't reliably render a preview unless you change your approach.",[36,196,198],{"id":197},"the-solution-generate-a-poster-image-from-the-video-blob","✅ The Solution: Generate a Poster Image from the Video Blob",[14,200,201,202,204],{},"Instead of relying on the ",[28,203,154],{}," tag to render the first frame (which may fail on iOS), we manually generate a poster (thumbnail) from the uploaded Blob using JavaScript.",[206,207,209],"h3",{"id":208},"benefits-of-this-approach","Benefits of This Approach:",[124,211,212,215,218],{},[127,213,214],{},"✔️ Works across all platforms, including iOS Safari",[127,216,217],{},"✔️ Provides a consistent visual preview",[127,219,220],{},"✔️ Avoids unexpected blank states or failed metadata loading",[36,222,224],{"id":223},"implementation-generateposterfromvideo","Implementation: generatePosterFromVideo()",[14,226,227],{},"Here's the utility I use to extract the first frame:",[44,229,233],{"className":230,"code":231,"language":232,"meta":49,"style":49},"language-typescript shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","export const generatePosterFromVideo = (videoBlob: Blob): Promise\u003Cstring> => {\n  return new Promise((resolve) => {\n    const video = document.createElement(\"video\");\n    const objectUrl = URL.createObjectURL(videoBlob);\n\n    video.src = objectUrl;\n    video.crossOrigin = \"anonymous\";\n    video.preload = \"metadata\";\n    video.muted = true;\n    video.playsInline = true;\n\n    const cleanup = () => URL.revokeObjectURL(objectUrl);\n\n    const timeoutId = setTimeout(() => {\n      cleanup();\n      resolve(\"\");\n    }, 2000);\n\n    video.onloadedmetadata = () => {\n      video.currentTime = 0.1;\n    };\n\n    video.onseeked = () => {\n      clearTimeout(timeoutId);\n\n      const canvas = document.createElement(\"canvas\");\n      canvas.width = video.videoWidth;\n      canvas.height = video.videoHeight;\n\n      const ctx = canvas.getContext(\"2d\");\n      if (ctx) {\n        ctx.drawImage(video, 0, 0);\n        const poster = canvas.toDataURL(\"image/jpeg\");\n        cleanup();\n        resolve(poster);\n      } else {\n        cleanup();\n        resolve(\"\");\n      }\n    };\n\n    video.onerror = () => {\n      clearTimeout(timeoutId);\n      cleanup();\n      resolve(\"\");\n    };\n  });\n};\n","typescript",[28,234,235,283,308,341,365,370,387,409,430,448,464,469,500,505,527,537,552,566,571,589,607,613,618,636,651,656,686,708,729,734,764,781,810,841,851,866,877,886,899,905,910,915,933,946,955,968,973,983],{"__ignoreMap":49},[53,236,237,241,244,247,249,252,256,259,263,266,269,271,274,277,280],{"class":55,"line":56},[53,238,240],{"class":239},"s7zQu","export",[53,242,243],{"class":59}," const",[53,245,246],{"class":63}," generatePosterFromVideo ",[53,248,68],{"class":67},[53,250,251],{"class":67}," (",[53,253,255],{"class":254},"sHdIc","videoBlob",[53,257,258],{"class":67},":",[53,260,262],{"class":261},"sBMFI"," Blob",[53,264,265],{"class":67},"):",[53,267,268],{"class":261}," Promise",[53,270,97],{"class":67},[53,272,273],{"class":261},"string",[53,275,276],{"class":67},">",[53,278,279],{"class":59}," =>",[53,281,282],{"class":67}," {\n",[53,284,285,288,291,293,296,298,301,304,306],{"class":55,"line":87},[53,286,287],{"class":239},"  return",[53,289,290],{"class":67}," new",[53,292,268],{"class":261},[53,294,295],{"class":100},"(",[53,297,295],{"class":67},[53,299,300],{"class":254},"resolve",[53,302,303],{"class":67},")",[53,305,279],{"class":59},[53,307,282],{"class":67},[53,309,310,313,316,319,322,324,327,329,332,335,337,339],{"class":55,"line":94},[53,311,312],{"class":59},"    const",[53,314,315],{"class":63}," video",[53,317,318],{"class":67}," =",[53,320,321],{"class":63}," document",[53,323,74],{"class":67},[53,325,326],{"class":77},"createElement",[53,328,295],{"class":100},[53,330,331],{"class":67},"\"",[53,333,101],{"class":334},"sfazB",[53,336,331],{"class":67},[53,338,303],{"class":100},[53,340,84],{"class":67},[53,342,344,346,349,351,353,355,357,359,361,363],{"class":55,"line":343},4,[53,345,312],{"class":59},[53,347,348],{"class":63}," objectUrl",[53,350,318],{"class":67},[53,352,71],{"class":63},[53,354,74],{"class":67},[53,356,78],{"class":77},[53,358,295],{"class":100},[53,360,255],{"class":63},[53,362,303],{"class":100},[53,364,84],{"class":67},[53,366,368],{"class":55,"line":367},5,[53,369,91],{"emptyLinePlaceholder":90},[53,371,373,376,378,381,383,385],{"class":55,"line":372},6,[53,374,375],{"class":63},"    video",[53,377,74],{"class":67},[53,379,380],{"class":63},"src",[53,382,318],{"class":67},[53,384,348],{"class":63},[53,386,84],{"class":67},[53,388,390,392,394,397,399,402,405,407],{"class":55,"line":389},7,[53,391,375],{"class":63},[53,393,74],{"class":67},[53,395,396],{"class":63},"crossOrigin",[53,398,318],{"class":67},[53,400,401],{"class":67}," \"",[53,403,404],{"class":334},"anonymous",[53,406,331],{"class":67},[53,408,84],{"class":67},[53,410,412,414,416,419,421,423,426,428],{"class":55,"line":411},8,[53,413,375],{"class":63},[53,415,74],{"class":67},[53,417,418],{"class":63},"preload",[53,420,318],{"class":67},[53,422,401],{"class":67},[53,424,425],{"class":334},"metadata",[53,427,331],{"class":67},[53,429,84],{"class":67},[53,431,433,435,437,440,442,446],{"class":55,"line":432},9,[53,434,375],{"class":63},[53,436,74],{"class":67},[53,438,439],{"class":63},"muted",[53,441,318],{"class":67},[53,443,445],{"class":444},"sfNiH"," true",[53,447,84],{"class":67},[53,449,451,453,455,458,460,462],{"class":55,"line":450},10,[53,452,375],{"class":63},[53,454,74],{"class":67},[53,456,457],{"class":63},"playsInline",[53,459,318],{"class":67},[53,461,445],{"class":444},[53,463,84],{"class":67},[53,465,467],{"class":55,"line":466},11,[53,468,91],{"emptyLinePlaceholder":90},[53,470,472,474,477,479,482,484,486,488,491,493,496,498],{"class":55,"line":471},12,[53,473,312],{"class":59},[53,475,476],{"class":63}," cleanup",[53,478,318],{"class":67},[53,480,481],{"class":67}," ()",[53,483,279],{"class":59},[53,485,71],{"class":63},[53,487,74],{"class":67},[53,489,490],{"class":77},"revokeObjectURL",[53,492,295],{"class":100},[53,494,495],{"class":63},"objectUrl",[53,497,303],{"class":100},[53,499,84],{"class":67},[53,501,503],{"class":55,"line":502},13,[53,504,91],{"emptyLinePlaceholder":90},[53,506,508,510,513,515,518,520,523,525],{"class":55,"line":507},14,[53,509,312],{"class":59},[53,511,512],{"class":63}," timeoutId",[53,514,318],{"class":67},[53,516,517],{"class":77}," setTimeout",[53,519,295],{"class":100},[53,521,522],{"class":67},"()",[53,524,279],{"class":59},[53,526,282],{"class":67},[53,528,530,533,535],{"class":55,"line":529},15,[53,531,532],{"class":77},"      cleanup",[53,534,522],{"class":100},[53,536,84],{"class":67},[53,538,540,543,545,548,550],{"class":55,"line":539},16,[53,541,542],{"class":77},"      resolve",[53,544,295],{"class":100},[53,546,547],{"class":67},"\"\"",[53,549,303],{"class":100},[53,551,84],{"class":67},[53,553,555,558,562,564],{"class":55,"line":554},17,[53,556,557],{"class":67},"    },",[53,559,561],{"class":560},"sbssI"," 2000",[53,563,303],{"class":100},[53,565,84],{"class":67},[53,567,569],{"class":55,"line":568},18,[53,570,91],{"emptyLinePlaceholder":90},[53,572,574,576,578,581,583,585,587],{"class":55,"line":573},19,[53,575,375],{"class":63},[53,577,74],{"class":67},[53,579,580],{"class":77},"onloadedmetadata",[53,582,318],{"class":67},[53,584,481],{"class":67},[53,586,279],{"class":59},[53,588,282],{"class":67},[53,590,592,595,597,600,602,605],{"class":55,"line":591},20,[53,593,594],{"class":63},"      video",[53,596,74],{"class":67},[53,598,599],{"class":63},"currentTime",[53,601,318],{"class":67},[53,603,604],{"class":560}," 0.1",[53,606,84],{"class":67},[53,608,610],{"class":55,"line":609},21,[53,611,612],{"class":67},"    };\n",[53,614,616],{"class":55,"line":615},22,[53,617,91],{"emptyLinePlaceholder":90},[53,619,621,623,625,628,630,632,634],{"class":55,"line":620},23,[53,622,375],{"class":63},[53,624,74],{"class":67},[53,626,627],{"class":77},"onseeked",[53,629,318],{"class":67},[53,631,481],{"class":67},[53,633,279],{"class":59},[53,635,282],{"class":67},[53,637,639,642,644,647,649],{"class":55,"line":638},24,[53,640,641],{"class":77},"      clearTimeout",[53,643,295],{"class":100},[53,645,646],{"class":63},"timeoutId",[53,648,303],{"class":100},[53,650,84],{"class":67},[53,652,654],{"class":55,"line":653},25,[53,655,91],{"emptyLinePlaceholder":90},[53,657,659,662,665,667,669,671,673,675,677,680,682,684],{"class":55,"line":658},26,[53,660,661],{"class":59},"      const",[53,663,664],{"class":63}," canvas",[53,666,318],{"class":67},[53,668,321],{"class":63},[53,670,74],{"class":67},[53,672,326],{"class":77},[53,674,295],{"class":100},[53,676,331],{"class":67},[53,678,679],{"class":334},"canvas",[53,681,331],{"class":67},[53,683,303],{"class":100},[53,685,84],{"class":67},[53,687,689,692,694,697,699,701,703,706],{"class":55,"line":688},27,[53,690,691],{"class":63},"      canvas",[53,693,74],{"class":67},[53,695,696],{"class":63},"width",[53,698,318],{"class":67},[53,700,315],{"class":63},[53,702,74],{"class":67},[53,704,705],{"class":63},"videoWidth",[53,707,84],{"class":67},[53,709,711,713,715,718,720,722,724,727],{"class":55,"line":710},28,[53,712,691],{"class":63},[53,714,74],{"class":67},[53,716,717],{"class":63},"height",[53,719,318],{"class":67},[53,721,315],{"class":63},[53,723,74],{"class":67},[53,725,726],{"class":63},"videoHeight",[53,728,84],{"class":67},[53,730,732],{"class":55,"line":731},29,[53,733,91],{"emptyLinePlaceholder":90},[53,735,737,739,742,744,746,748,751,753,755,758,760,762],{"class":55,"line":736},30,[53,738,661],{"class":59},[53,740,741],{"class":63}," ctx",[53,743,318],{"class":67},[53,745,664],{"class":63},[53,747,74],{"class":67},[53,749,750],{"class":77},"getContext",[53,752,295],{"class":100},[53,754,331],{"class":67},[53,756,757],{"class":334},"2d",[53,759,331],{"class":67},[53,761,303],{"class":100},[53,763,84],{"class":67},[53,765,767,770,772,775,778],{"class":55,"line":766},31,[53,768,769],{"class":239},"      if",[53,771,251],{"class":100},[53,773,774],{"class":63},"ctx",[53,776,777],{"class":100},") ",[53,779,780],{"class":67},"{\n",[53,782,784,787,789,792,794,796,799,802,804,806,808],{"class":55,"line":783},32,[53,785,786],{"class":63},"        ctx",[53,788,74],{"class":67},[53,790,791],{"class":77},"drawImage",[53,793,295],{"class":100},[53,795,101],{"class":63},[53,797,798],{"class":67},",",[53,800,801],{"class":560}," 0",[53,803,798],{"class":67},[53,805,801],{"class":560},[53,807,303],{"class":100},[53,809,84],{"class":67},[53,811,813,816,819,821,823,825,828,830,832,835,837,839],{"class":55,"line":812},33,[53,814,815],{"class":59},"        const",[53,817,818],{"class":63}," poster",[53,820,318],{"class":67},[53,822,664],{"class":63},[53,824,74],{"class":67},[53,826,827],{"class":77},"toDataURL",[53,829,295],{"class":100},[53,831,331],{"class":67},[53,833,834],{"class":334},"image/jpeg",[53,836,331],{"class":67},[53,838,303],{"class":100},[53,840,84],{"class":67},[53,842,844,847,849],{"class":55,"line":843},34,[53,845,846],{"class":77},"        cleanup",[53,848,522],{"class":100},[53,850,84],{"class":67},[53,852,854,857,859,862,864],{"class":55,"line":853},35,[53,855,856],{"class":77},"        resolve",[53,858,295],{"class":100},[53,860,861],{"class":63},"poster",[53,863,303],{"class":100},[53,865,84],{"class":67},[53,867,869,872,875],{"class":55,"line":868},36,[53,870,871],{"class":67},"      }",[53,873,874],{"class":239}," else",[53,876,282],{"class":67},[53,878,880,882,884],{"class":55,"line":879},37,[53,881,846],{"class":77},[53,883,522],{"class":100},[53,885,84],{"class":67},[53,887,889,891,893,895,897],{"class":55,"line":888},38,[53,890,856],{"class":77},[53,892,295],{"class":100},[53,894,547],{"class":67},[53,896,303],{"class":100},[53,898,84],{"class":67},[53,900,902],{"class":55,"line":901},39,[53,903,904],{"class":67},"      }\n",[53,906,908],{"class":55,"line":907},40,[53,909,612],{"class":67},[53,911,913],{"class":55,"line":912},41,[53,914,91],{"emptyLinePlaceholder":90},[53,916,918,920,922,925,927,929,931],{"class":55,"line":917},42,[53,919,375],{"class":63},[53,921,74],{"class":67},[53,923,924],{"class":77},"onerror",[53,926,318],{"class":67},[53,928,481],{"class":67},[53,930,279],{"class":59},[53,932,282],{"class":67},[53,934,936,938,940,942,944],{"class":55,"line":935},43,[53,937,641],{"class":77},[53,939,295],{"class":100},[53,941,646],{"class":63},[53,943,303],{"class":100},[53,945,84],{"class":67},[53,947,949,951,953],{"class":55,"line":948},44,[53,950,532],{"class":77},[53,952,522],{"class":100},[53,954,84],{"class":67},[53,956,958,960,962,964,966],{"class":55,"line":957},45,[53,959,542],{"class":77},[53,961,295],{"class":100},[53,963,547],{"class":67},[53,965,303],{"class":100},[53,967,84],{"class":67},[53,969,971],{"class":55,"line":970},46,[53,972,612],{"class":67},[53,974,976,979,981],{"class":55,"line":975},47,[53,977,978],{"class":67},"  }",[53,980,303],{"class":100},[53,982,84],{"class":67},[53,984,986],{"class":55,"line":985},48,[53,987,988],{"class":67},"};\n",[36,990,992],{"id":991},"integrating-poster-generation-in-your-react-component","Integrating Poster Generation in Your React Component",[14,994,995,996,999],{},"In your component (e.g., ",[28,997,998],{},"MediaPreview.tsx","), you can use this function to generate and apply the poster when the preview is a Blob:",[44,1001,1003],{"className":230,"code":1002,"language":232,"meta":49,"style":49},"useEffect(() => {\n  if (type === \"video\" && preview?.startsWith(\"blob:\")) {\n    fetch(preview)\n      .then((r) => r.blob())\n      .then((blob) => generatePosterFromVideo(blob))\n      .then((poster) => setPosterImage(poster || null))\n      .catch(() => setPosterImage(null));\n  }\n}, [type, preview]);\n",[28,1004,1005,1018,1062,1075,1105,1131,1162,1187,1192],{"__ignoreMap":49},[53,1006,1007,1010,1012,1014,1016],{"class":55,"line":56},[53,1008,1009],{"class":77},"useEffect",[53,1011,295],{"class":63},[53,1013,522],{"class":67},[53,1015,279],{"class":59},[53,1017,282],{"class":67},[53,1019,1020,1023,1025,1028,1031,1033,1035,1037,1040,1043,1046,1049,1051,1053,1055,1057,1060],{"class":55,"line":87},[53,1021,1022],{"class":239},"  if",[53,1024,251],{"class":100},[53,1026,1027],{"class":63},"type",[53,1029,1030],{"class":67}," ===",[53,1032,401],{"class":67},[53,1034,101],{"class":334},[53,1036,331],{"class":67},[53,1038,1039],{"class":67}," &&",[53,1041,1042],{"class":63}," preview",[53,1044,1045],{"class":67},"?.",[53,1047,1048],{"class":77},"startsWith",[53,1050,295],{"class":100},[53,1052,331],{"class":67},[53,1054,30],{"class":334},[53,1056,331],{"class":67},[53,1058,1059],{"class":100},")) ",[53,1061,780],{"class":67},[53,1063,1064,1067,1069,1072],{"class":55,"line":94},[53,1065,1066],{"class":77},"    fetch",[53,1068,295],{"class":100},[53,1070,1071],{"class":63},"preview",[53,1073,1074],{"class":100},")\n",[53,1076,1077,1080,1083,1085,1087,1090,1092,1094,1097,1099,1102],{"class":55,"line":343},[53,1078,1079],{"class":67},"      .",[53,1081,1082],{"class":77},"then",[53,1084,295],{"class":100},[53,1086,295],{"class":67},[53,1088,1089],{"class":254},"r",[53,1091,303],{"class":67},[53,1093,279],{"class":59},[53,1095,1096],{"class":63}," r",[53,1098,74],{"class":67},[53,1100,1101],{"class":77},"blob",[53,1103,1104],{"class":100},"())\n",[53,1106,1107,1109,1111,1113,1115,1117,1119,1121,1124,1126,1128],{"class":55,"line":367},[53,1108,1079],{"class":67},[53,1110,1082],{"class":77},[53,1112,295],{"class":100},[53,1114,295],{"class":67},[53,1116,1101],{"class":254},[53,1118,303],{"class":67},[53,1120,279],{"class":59},[53,1122,1123],{"class":77}," generatePosterFromVideo",[53,1125,295],{"class":100},[53,1127,1101],{"class":63},[53,1129,1130],{"class":100},"))\n",[53,1132,1133,1135,1137,1139,1141,1143,1145,1147,1150,1152,1154,1157,1160],{"class":55,"line":372},[53,1134,1079],{"class":67},[53,1136,1082],{"class":77},[53,1138,295],{"class":100},[53,1140,295],{"class":67},[53,1142,861],{"class":254},[53,1144,303],{"class":67},[53,1146,279],{"class":59},[53,1148,1149],{"class":77}," setPosterImage",[53,1151,295],{"class":100},[53,1153,861],{"class":63},[53,1155,1156],{"class":67}," ||",[53,1158,1159],{"class":67}," null",[53,1161,1130],{"class":100},[53,1163,1164,1166,1169,1171,1173,1175,1177,1179,1182,1185],{"class":55,"line":389},[53,1165,1079],{"class":67},[53,1167,1168],{"class":77},"catch",[53,1170,295],{"class":100},[53,1172,522],{"class":67},[53,1174,279],{"class":59},[53,1176,1149],{"class":77},[53,1178,295],{"class":100},[53,1180,1181],{"class":67},"null",[53,1183,1184],{"class":100},"))",[53,1186,84],{"class":67},[53,1188,1189],{"class":55,"line":411},[53,1190,1191],{"class":67},"  }\n",[53,1193,1194,1197,1200,1202,1205],{"class":55,"line":432},[53,1195,1196],{"class":67},"},",[53,1198,1199],{"class":63}," [type",[53,1201,798],{"class":67},[53,1203,1204],{"class":63}," preview])",[53,1206,84],{"class":67},[14,1208,1209,1210,1212],{},"And then apply it to the ",[28,1211,154],{}," element:",[44,1214,1218],{"className":1215,"code":1216,"language":1217,"meta":49,"style":49},"language-jsx shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\u003Cvideo\n  src={preview}\n  poster={posterImage || undefined}\n  controls\n  playsInline\n  preload=\"metadata\"\n/>\n","jsx",[28,1219,1220,1227,1239,1255,1260,1265,1279],{"__ignoreMap":49},[53,1221,1222,1224],{"class":55,"line":56},[53,1223,97],{"class":67},[53,1225,1226],{"class":100},"video\n",[53,1228,1229,1232,1234,1236],{"class":55,"line":87},[53,1230,1231],{"class":59},"  src",[53,1233,107],{"class":67},[53,1235,1071],{"class":63},[53,1237,1238],{"class":67},"}\n",[53,1240,1241,1244,1246,1249,1252],{"class":55,"line":94},[53,1242,1243],{"class":59},"  poster",[53,1245,107],{"class":67},[53,1247,1248],{"class":63},"posterImage ",[53,1250,1251],{"class":67},"||",[53,1253,1254],{"class":67}," undefined}\n",[53,1256,1257],{"class":55,"line":343},[53,1258,1259],{"class":59},"  controls\n",[53,1261,1262],{"class":55,"line":367},[53,1263,1264],{"class":59},"  playsInline\n",[53,1266,1267,1270,1272,1274,1276],{"class":55,"line":372},[53,1268,1269],{"class":59},"  preload",[53,1271,68],{"class":67},[53,1273,331],{"class":67},[53,1275,425],{"class":334},[53,1277,1278],{"class":67},"\"\n",[53,1280,1281],{"class":55,"line":389},[53,1282,1283],{"class":67},"/>\n",[36,1285,1287],{"id":1286},"cleanup-revoke-blob-urls-to-prevent-memory-leaks","Cleanup: Revoke Blob URLs to Prevent Memory Leaks",[14,1289,1290],{},"Always release memory tied to Blob URLs when the component unmounts:",[44,1292,1294],{"className":230,"code":1293,"language":232,"meta":49,"style":49},"useEffect(() => {\n  return () => {\n    if (preview?.startsWith(\"blob:\")) {\n      URL.revokeObjectURL(preview);\n    }\n  };\n}, [preview]);\n",[28,1295,1296,1308,1318,1343,1360,1365,1370],{"__ignoreMap":49},[53,1297,1298,1300,1302,1304,1306],{"class":55,"line":56},[53,1299,1009],{"class":77},[53,1301,295],{"class":63},[53,1303,522],{"class":67},[53,1305,279],{"class":59},[53,1307,282],{"class":67},[53,1309,1310,1312,1314,1316],{"class":55,"line":87},[53,1311,287],{"class":239},[53,1313,481],{"class":67},[53,1315,279],{"class":59},[53,1317,282],{"class":67},[53,1319,1320,1323,1325,1327,1329,1331,1333,1335,1337,1339,1341],{"class":55,"line":94},[53,1321,1322],{"class":239},"    if",[53,1324,251],{"class":100},[53,1326,1071],{"class":63},[53,1328,1045],{"class":67},[53,1330,1048],{"class":77},[53,1332,295],{"class":100},[53,1334,331],{"class":67},[53,1336,30],{"class":334},[53,1338,331],{"class":67},[53,1340,1059],{"class":100},[53,1342,780],{"class":67},[53,1344,1345,1348,1350,1352,1354,1356,1358],{"class":55,"line":343},[53,1346,1347],{"class":63},"      URL",[53,1349,74],{"class":67},[53,1351,490],{"class":77},[53,1353,295],{"class":100},[53,1355,1071],{"class":63},[53,1357,303],{"class":100},[53,1359,84],{"class":67},[53,1361,1362],{"class":55,"line":367},[53,1363,1364],{"class":67},"    }\n",[53,1366,1367],{"class":55,"line":372},[53,1368,1369],{"class":67},"  };\n",[53,1371,1372,1374,1377],{"class":55,"line":389},[53,1373,1196],{"class":67},[53,1375,1376],{"class":63}," [preview])",[53,1378,84],{"class":67},[36,1380,1382],{"id":1381},"why-this-works-on-ios-and-the-native-method-fails","Why This Works on iOS (And the Native Method Fails)",[14,1384,1385],{},"iOS Safari requires more explicit handling for video playback and previewing:",[124,1387,1388,1391,1397],{},[127,1389,1390],{},"Blob URLs may not load correctly into video elements.",[127,1392,1393,1394,1396],{},"Videos must be muted and use ",[28,1395,457],{}," to avoid autoplay blocking.",[127,1398,1399],{},"Seeking to a timestamp immediately after loading metadata is unreliable.",[14,1401,1402],{},"By using canvas to capture a frame manually, we completely bypass these limitations and provide a consistent fallback.",[36,1404,1406],{"id":1405},"final-result-reliable-video-previews-across-all-devices","Final Result: Reliable Video Previews Across All Devices",[14,1408,1409],{},"With this solution:",[124,1411,1412,1415,1418],{},[127,1413,1414],{},"✅ Blob-based video previews work on iOS",[127,1416,1417],{},"✅ Users see an accurate thumbnail before uploading",[127,1419,1420],{},"✅ Preview logic works the same on desktop, Android, and iOS",[14,1422,1423],{},"Don't let iOS quirks break your preview flow! 🚀",[1425,1426],"hr",{},[36,1428,1430],{"id":1429},"key-takeaways","Key Takeaways",[1432,1433,1434,1440,1449,1455,1466],"ol",{},[127,1435,1436,1439],{},[166,1437,1438],{},"iOS Safari has limitations"," with Blob URLs in video elements",[127,1441,1442,1445,1446],{},[166,1443,1444],{},"Generate poster images manually"," using canvas and ",[28,1447,1448],{},"drawImage()",[127,1450,1451,1454],{},[166,1452,1453],{},"Always cleanup"," Blob URLs to prevent memory leaks",[127,1456,1457,1465],{},[166,1458,1459,1460,1462,1463],{},"Use ",[28,1461,457],{}," and ",[28,1464,439],{}," for better iOS compatibility",[127,1467,1468,1471],{},[166,1469,1470],{},"Test on real iOS devices"," — simulators may behave differently",[14,1473,1474],{},"Have you encountered similar iOS-specific issues? Share your experience in the comments below! 💬",[1476,1477,1478],"style",{},"html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}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 .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 pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sfNiH, html code.shiki .sfNiH{--shiki-light:#FF5370;--shiki-default:#FF9CAC;--shiki-dark:#FF9CAC}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}",{"title":49,"searchDepth":87,"depth":87,"links":1480},[1481,1482,1483,1486,1487,1488,1489,1490,1491],{"id":38,"depth":87,"text":39},{"id":176,"depth":87,"text":177},{"id":197,"depth":87,"text":198,"children":1484},[1485],{"id":208,"depth":94,"text":209},{"id":223,"depth":87,"text":224},{"id":991,"depth":87,"text":992},{"id":1286,"depth":87,"text":1287},{"id":1381,"depth":87,"text":1382},{"id":1405,"depth":87,"text":1406},{"id":1429,"depth":87,"text":1430},"Fix video blob URL preview issues on iOS Safari in React and Next.js apps. Step-by-step guide covering objectURL, poster generation, and mobile video upload bugs.","md",{"author":1495,"date":1496,"image":1497,"category":1498,"tags":1499,"featured":1512,"draft":1512},"Aysegul Karadan","2025-08-12T17:40:00.000+03:00","/img/video-blob-ios/cover.webp","Web Development",[1500,1501,1502,1503,101,1101,1504,1505,1506,1507,1508,1509,1510,1511],"react","nextjs","ios","safari","tutorial","ios-safari-video-fix","video-blob-url-react","nextjs-ios-video-bug","mobile-video-upload-fix","react-video-preview-ios","blob-url-not-working-ios","how-to-fix-video-preview-ios",false,"/blog/fix-video-blob-previews-ios-react-nextjs",{"title":5,"description":1492},{"loc":1513},"blog/fix-video-blob-previews-ios-react-nextjs","gcE9kF1tN3LhTrHqDDsWGFBwi-su6sYy85i7YmA9b3M",1782986781100]