Back to Blog
Web Development

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.

WonderCoder Team
WonderCoder Team
5 min read
#react #nextjs #ios #safari #video #blob #tutorial

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 playsInline to 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

  1. iOS Safari has limitations with Blob URLs in video elements
  2. Generate poster images manually using canvas and drawImage()
  3. Always cleanup Blob URLs to prevent memory leaks
  4. Use playsInline and muted for better iOS compatibility
  5. Test on real iOS devices — simulators may behave differently

Have you encountered similar iOS-specific issues? Share your experience in the comments below! 💬

WonderCoder Team

WonderCoder Team

Content Creator at WonderCoder. Passionate about modern web development and sharing knowledge with the community.

Share this post

Help others discover this content

Enjoyed this post?

Check out more articles on our blog

View All Posts
WonderCoder Logo WonderCoder

⚡ Create. Explore. Evolve. Make Something New Every Day.

Connect

© 2026 WonderCoder. All rights reserved.