Build a WebAssembly-Powered Image Optimizer in the Browser (No Server Required)

Most devs reach for Cloudinary, Squoosh CLI, or a Node backend to optimize images. But what if you could do production-grade image compression — including WebP and MozJPEG — directly in the browser, using WebAssembly? No server, no upload, no cost. Here’s how to build your own browser-based image optimizer using WebAssembly and wasm-bindgen. Step 1: Use precompiled WASM codecs via Squoosh Google’s Squoosh project exposes production-ready WASM image codecs like MozJPEG, WebP, and AVIF — and you can use them in your own app. Install the core codecs: npm install @squoosh/lib Then load a codec like MozJPEG: import { ImagePool } from '@squoosh/lib'; const imagePool = new ImagePool(); Step 2: Accept user uploads and compress in-browser Set up an input to handle image uploads: Now optimize the file: async function handleFile(event) { const file = event.target.files[0]; const arrayBuffer = await file.arrayBuffer(); const image = imagePool.ingestImage(arrayBuffer); await image.encode({ mozjpeg: { quality: 70, }, }); const encoded = await image.encodedWith.mozjpeg; const blob = new Blob([encoded.binary], { type: 'image/jpeg' }); // Optionally download it const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'optimized.jpg'; a.click(); } Step 3: Support Other Codecs (WebP, AVIF, etc.) Swap mozjpeg for webp or avif to support modern formats: await image.encode({ webp: { quality: 80, }, }); You can even expose a UI to let users choose the output format. Step 4: Optional React Hook for Integration To make this reusable across your app, extract it into a hook: function useImageOptimizer() { const imagePool = useMemo(() => new ImagePool(), []); const optimize = async (file, format = 'mozjpeg') => { const arrayBuffer = await file.arrayBuffer(); const image = imagePool.ingestImage(arrayBuffer); await image.encode({ [format]: { quality: 75 } }); const encoded = await image.encodedWith[format]; return new Blob([encoded.binary], { type: \`image/\${format}\` }); }; return { optimize }; } ✅ Pros: Completely serverless — all image processing happens client-side Supports WebP, MozJPEG, and AVIF with production-quality compression Great for PWAs, CMS apps, or privacy-first tools (no upload) ⚠️ Cons: WASM codecs are large (~1MB+), so initial load is heavier Processing is memory/CPU intensive — slower on low-end devices Not ideal for batch-processing large volumes Summary With just a few lines of JavaScript and Google’s prebuilt WASM codecs, you can build a browser-based image optimizer that rivals many SaaS tools. This approach keeps images private, requires no backend infrastructure, and provides full control over format and quality. It’s ideal for privacy-sensitive apps, PWAs, or anytime you want to offload work to the client without sacrificing performance. Next time you consider reaching for a third-party API or spinning up a Lambda just to optimize images, remember — the browser can do it all. If this was helpful, you can support me here: Buy Me a Coffee ☕

Apr 29, 2025 - 06:59
 0
Build a WebAssembly-Powered Image Optimizer in the Browser (No Server Required)

Most devs reach for Cloudinary, Squoosh CLI, or a Node backend to optimize images.

But what if you could do production-grade image compression — including WebP and MozJPEG — directly in the browser, using WebAssembly? No server, no upload, no cost.

Here’s how to build your own browser-based image optimizer using WebAssembly and wasm-bindgen.

Step 1: Use precompiled WASM codecs via Squoosh

Google’s Squoosh project exposes production-ready WASM image codecs like MozJPEG, WebP, and AVIF — and you can use them in your own app.

Install the core codecs:

npm install @squoosh/lib

Then load a codec like MozJPEG:

import { ImagePool } from '@squoosh/lib';

const imagePool = new ImagePool();

Step 2: Accept user uploads and compress in-browser

Set up an input to handle image uploads:


Now optimize the file:

async function handleFile(event) {
  const file = event.target.files[0];
  const arrayBuffer = await file.arrayBuffer();
  
  const image = imagePool.ingestImage(arrayBuffer);
  await image.encode({
    mozjpeg: {
      quality: 70,
    },
  });

  const encoded = await image.encodedWith.mozjpeg;
  const blob = new Blob([encoded.binary], { type: 'image/jpeg' });

  // Optionally download it
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url;
  a.download = 'optimized.jpg';
  a.click();
}

Step 3: Support Other Codecs (WebP, AVIF, etc.)

Swap mozjpeg for webp or avif to support modern formats:

await image.encode({
  webp: {
    quality: 80,
  },
});

You can even expose a UI to let users choose the output format.

Step 4: Optional React Hook for Integration

To make this reusable across your app, extract it into a hook:

function useImageOptimizer() {
  const imagePool = useMemo(() => new ImagePool(), []);

  const optimize = async (file, format = 'mozjpeg') => {
    const arrayBuffer = await file.arrayBuffer();
    const image = imagePool.ingestImage(arrayBuffer);
    await image.encode({ [format]: { quality: 75 } });
    const encoded = await image.encodedWith[format];
    return new Blob([encoded.binary], { type: \`image/\${format}\` });
  };

  return { optimize };
}

Pros:

  • Completely serverless — all image processing happens client-side
  • Supports WebP, MozJPEG, and AVIF with production-quality compression
  • Great for PWAs, CMS apps, or privacy-first tools (no upload)

⚠️ Cons:

  • WASM codecs are large (~1MB+), so initial load is heavier
  • Processing is memory/CPU intensive — slower on low-end devices
  • Not ideal for batch-processing large volumes

Summary

With just a few lines of JavaScript and Google’s prebuilt WASM codecs, you can build a browser-based image optimizer that rivals many SaaS tools. This approach keeps images private, requires no backend infrastructure, and provides full control over format and quality. It’s ideal for privacy-sensitive apps, PWAs, or anytime you want to offload work to the client without sacrificing performance.

Next time you consider reaching for a third-party API or spinning up a Lambda just to optimize images, remember — the browser can do it all.

If this was helpful, you can support me here: Buy Me a Coffee