DEV Community

Anass Laribi
Anass Laribi

Posted on

How I Built a 100% Client-Side 8MB Video Compressor in Next.js with WebAssembly

Online video compressors are usually frustrating: you have to upload a 100MB personal video to a remote cloud server, wait in an artificial processing queue, deal with intrusive popup ads, and download a watermarked video.

I wanted to see if modern browser technology was fast enough to run desktop-grade video compression entirely on the user's computer with zero server uploads.

Here is the technical breakdown of how I built clipshrink.com using Next.js 16, React 19, and FFmpeg compiled to WebAssembly (WASM).


  1. The Architecture: In-Browser Sandboxing

Traditional cloud tools rely on multi-core backend server clusters. ClipShrink takes the opposite approach: **0 bytes ever leave the user's device.

[User Selects Video (.mp4, .mov, .webm)]


[Local Browser RAM Sandbox] ◄─── (0 bytes sent to external cloud servers)


[Dynamic Target Bitrate Math] ◄─── Bitrate = (Target MB × 8192) / Duration


[FFmpeg WebAssembly Pipeline] ◄─── Local CPU software re-encoding


[Instant Local MP4 Download] ◄─── Encoded with '+faststart' web streaming


  1. The Tech Stack

Framework: Next.js 16 (App Router)
Interactive Core: @ffmpeg/ffmpeg and @ffmpeg/util
Styling: Tailwind CSS v4
Hosting:** Vercel (Edge network)


  1. Engineering Challenges & Solutions

A. Eliminating Third-Party CDN Latency
Originally, the app loaded @ffmpeg/core from public CDNs (unpkg.com). This created a single point of failure: privacy extensions and ad-blockers regularly blocked external CDN requests.

The Solution: Self-hosting the .wasm and .js binaries in the /public/ffmpeg/ directory with immutable edge caching:


typescript
await ffmpeg.load({
  coreURL: await toBlobURL("/ffmpeg/ffmpeg-core.js", "text/javascript"),
  wasmURL: await toBlobURL("/ffmpeg/ffmpeg-core.wasm", "application/wasm"),
});

B. Mobile Safari Memory Guard
iOS Safari enforces a strict WebAssembly heap allocation limit (typically 1GB–1.5GB). Attempting to process a 4K 60fps file directly in browser memory causes the tab to crash.
To protect the user experience, we implemented client-side file size guards before allocating memory:

const isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
if (isMobile && file.size > 150 * 1024 * 1024) {
  setError("Files over 150MB may exceed mobile browser memory. Please use a desktop browser.");
  return;
}

C. Fast Bilinear Downscaling
4K video has 8.3 million pixels per frame. Using FFmpeg's default bicubic scaler inside single-threaded WebAssembly resulted in long encoding times.
By introducing -sws_flags fast_bilinear and -preset ultrafast, we cut downscaling CPU cycles by 60% to 70% while maintaining crisp 720p HD output for platform limits like:
Discord 8MB Limit
WhatsApp 16MB Limit
Gmail / Outlook 25MB Attachments




Enter fullscreen mode Exit fullscreen mode

Top comments (1)

Some comments may only be visible to logged-in visitors. Sign in to view all comments.