How to Fix Video Blob Previews on iOS in React and Next.js
Learn how to fix video preview issues on iOS Safari when users upload videos in your React or Next.js app using blob URLs and custom poster generation.
How to Fix Video Blob Previews on iOS in React and Next.js

Are video previews not working on iOS when users upload a video in your web app?
You're not alone. This is a common issue developers face — especially when dealing with blob: URLs for client-side video previews in React or Next.js.
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.
❌ The Problem: Video Blob Preview Fails on iOS Safari
In most modern browsers, previewing a video before uploading it is straightforward:
const previewUrl = URL.createObjectURL(file);
<video src={previewUrl} controls />;
This approach works in:
- 🟢 Chrome
- 🟢 Firefox
- 🟢 Android browsers
But fails in:
- 🔴 iOS Safari
You might notice:
- The
<video>tag remains blank or doesn't load. - The first frame doesn't render.
- Metadata like duration never loads.
💡 Important: This is not a styling issue (e.g., className, size, or layout). It's a platform-specific limitation related to how iOS handles Blob URLs, video metadata, and memory.
Root Cause: iOS Handling of Blob URLs in Video Elements
iOS Safari has several security and performance restrictions that can cause Blob video previews to fail:
- Autoplay restrictions even with muted videos
- Limited support for rendering metadata from blob sources
- Inconsistent behavior when seeking in blob-loaded videos
As a result, you can't reliably render a preview unless you change your approach.
✅ The Solution: Generate a Poster Image from the Video Blob
Instead of relying on the <video> tag to render the first frame (which may fail on iOS), we manually generate a poster (thumbnail) from the uploaded Blob using JavaScript.
Benefits of This Approach:
- ✔️ Works across all platforms, including iOS Safari
- ✔️ Provides a consistent visual preview
- ✔️ Avoids unexpected blank states or failed metadata loading
Implementation: generatePosterFromVideo()
Here's the utility I use to extract the first frame:
export const generatePosterFromVideo = (videoBlob: Blob): Promise<string> => {
return new Promise((resolve) => {
const video = document.createElement("video");
const objectUrl = URL.createObjectURL(videoBlob);
video.src = objectUrl;
video.crossOrigin = "anonymous";
video.preload = "metadata";
video.muted = true;
video.playsInline = true;
const cleanup = () => URL.revokeObjectURL(objectUrl);
const timeoutId = setTimeout(() => {
cleanup();
resolve("");
}, 2000);
video.onloadedmetadata = () => {
video.currentTime = 0.1;
};
video.onseeked = () => {
clearTimeout(timeoutId);
const canvas = document.createElement("canvas");
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
const ctx = canvas.getContext("2d");
if (ctx) {
ctx.drawImage(video, 0, 0);
const poster = canvas.toDataURL("image/jpeg");
cleanup();
resolve(poster);
} else {
cleanup();
resolve("");
}
};
video.onerror = () => {
clearTimeout(timeoutId);
cleanup();
resolve("");
};
});
};
Integrating Poster Generation in Your React Component
In your component (e.g., MediaPreview.tsx), you can use this function to generate and apply the poster when the preview is a Blob:
useEffect(() => {
if (type === "video" && preview?.startsWith("blob:")) {
fetch(preview)
.then((r) => r.blob())
.then((blob) => generatePosterFromVideo(blob))
.then((poster) => setPosterImage(poster || null))
.catch(() => setPosterImage(null));
}
}, [type, preview]);
And then apply it to the <video> element:
<video
src={preview}
poster={posterImage || undefined}
controls
playsInline
preload="metadata"
/>
Cleanup: Revoke Blob URLs to Prevent Memory Leaks
Always release memory tied to Blob URLs when the component unmounts:
useEffect(() => {
return () => {
if (preview?.startsWith("blob:")) {
URL.revokeObjectURL(preview);
}
};
}, [preview]);
Why This Works on iOS (And the Native Method Fails)
iOS Safari requires more explicit handling for video playback and previewing:
- Blob URLs may not load correctly into video elements.
- Videos must be muted and use
playsInlineto avoid autoplay blocking. - Seeking to a timestamp immediately after loading metadata is unreliable.
By using canvas to capture a frame manually, we completely bypass these limitations and provide a consistent fallback.
Final Result: Reliable Video Previews Across All Devices
With this solution:
- ✅ Blob-based video previews work on iOS
- ✅ Users see an accurate thumbnail before uploading
- ✅ Preview logic works the same on desktop, Android, and iOS
Don't let iOS quirks break your preview flow! 🚀
Key Takeaways
- iOS Safari has limitations with Blob URLs in video elements
- Generate poster images manually using canvas and
drawImage() - Always cleanup Blob URLs to prevent memory leaks
- Use
playsInlineandmutedfor better iOS compatibility - Test on real iOS devices — simulators may behave differently
Have you encountered similar iOS-specific issues? Share your experience in the comments below! 💬
