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 ☕
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 ☕