cd /news/ai-tools/how-i-built-100-browser-based-image-… Β· home β€Ί topics β€Ί ai-tools β€Ί article
[ARTICLE Β· art-13877] src=dev.to pub= topic=ai-tools verified=true sentiment=↑ positive

How I Built 100 Browser-Based Image Tools With No Server (FFmpeg WASM, PDF-lib, AI Background Removal)

A developer built ImgToolkit, a collection of 100 browser-based image and video tools that process all files locally without uploading to any server. The project uses FFmpeg WASM for video processing, pdf-lib for PDF manipulation, and AI-powered background removal via ONNX models, all running entirely client-side through WebAssembly and the Canvas API. The developer overcame challenges including SharedArrayBuffer cross-origin isolation requirements and dynamic loading of heavy libraries to keep initial page load under 100KB of JavaScript.

read3 min publishedMay 25, 2026

When I started building ImgToolkit, the goal was simple: every image tool site I used either uploaded my files to some server I didn't trust, watermarked the output, or locked the useful features behind a $12/month plan.

I wanted to build something where everything runs in the browser. Your files never leave your device. No server, no account, no paywall.

This is the technical breakdown of how I got 100 tools working entirely client-side.

The core idea: the browser is powerful enough

Modern browsers have the Canvas API, WebAssembly, Web Workers, and file system access. With the right libraries, you can do things that felt server-only three years ago.

Here's the stack I settled on:

React + Vite β€” fast builds, lazy-loaded routes so each tool only loads what it needs

Canvas API β€” handles 80% of image operations (resize, crop, rotate, watermark, convert formats)

pdf-lib β€” pure JS PDF manipulation (merge, split, compress, add pages)

pdfjs-dist β€” PDF rendering to canvas (for PDF to JPG conversion)

FFmpeg WASM β€” video processing in the browser

@imgly/background-removal β€” AI background removal using ONNX models Tesseract.js β€” OCR, runs a full Tesseract engine via WASM

browser-image-compression β€” handles the heavy lifting for image compression

The interesting challenges

It works. But there are gotchas:

import { FFmpeg } from "@ffmpeg/ffmpeg";

import { fetchFile, toBlobURL } from "@ffmpeg/util";

const ffmpeg = new FFmpeg();

await ffmpeg.load({

coreURL: await toBlobURL(`/ffmpeg-core.js`

, "text/javascript"),

wasmURL: await toBlobURL(/ffmpeg-core.wasm , "application/wasm"),

});

await ffmpeg.writeFile("input.mp4", await fetchFile(file));

await ffmpeg.exec(["-i", "input.mp4", "-q:a", "0", "-map", "a", "output.mp3"]);

const data = await ffmpeg.readFile("output.mp3");

The WASM binary needs SharedArrayBuffer, which requires cross-origin isolation headers (COOP + COEP). Getting those headers right in production took longer than writing the actual tool.

Dynamic import on the FFmpeg tools was essential β€” you don't want 30MB on the homepage.

import { removeBackground } from "[@imgly](https://dev.to/imgly)/background-removal";

const blob = await removeBackground(imageFile, {

publicPath: "[https://cdn.imgly.com/background-removal/..](https://cdn.imgly.com/background-removal/..).",

model: "medium",

});

The result is a PNG with a transparent background, generated entirely in the user's browser using WebGL/WASM inference. No API key, no per-request cost, no server. The quality is genuinely good β€” comparable to early Remove.bg results.

The tricky part: onnxruntime-web must be installed as a direct dependency alongside the library, not just a peer dependency. Took me an embarrassingly long time to debug that.

import { PDFDocument } from "pdf-lib";

const mergedPdf = await PDFDocument.create();

for (const file of files) {

const bytes = await file.arrayBuffer();

const pdf = await PDFDocument.load(bytes);

const pages = await mergedPdf.copyPages(pdf, pdf.getPageIndices());

pages.forEach(p => mergedPdf.addPage(p));

}

const merged = await mergedPdf.save();

For "compress PDF", I re-encode all images inside the PDF at lower quality. Not perfect, but gets 30–60% size reduction on scanned documents without any server.

const RemoveBackground = lazy(() => import("@/pages/remove-background"));

const FfmpegVideoToMp3 = lazy(() => import("@/pages/video-to-mp3"));

Heavy libraries (FFmpeg, face-api, background-removal) are dynamically imported inside the page component, not at route level β€” so they only load when the user actually uses that tool.

Initial page load is under 100KB of JS. A user who only compresses images never downloads any FFmpeg or ONNX code.

ctx.filter = blur(${blurStrength}px) ;

ctx.drawImage(canvas, x, y, w, h, x, y, w, h);

ctx.filter = "none";

Works surprisingly well on photos with 1–4 faces. Degrades on crowds β€” but so does every commercial API at that task.

What I learned

The browser is ready. WebAssembly, ONNX inference, full PDF manipulation, video processing β€” it all works. The main limits are file size (very large files hit memory limits) and first-load time for WASM binaries.

Lazy is non-negotiable. Without it, you're shipping 50MB of JS to every visitor regardless of which tool they use.

Headers matter for WASM. SharedArrayBuffer requires COOP: same-origin and COEP: require-corp. Get these wrong and FFmpeg silently fails.

Client-side means private by default. Users immediately trust a tool more when you can prove their files never leave their device. It's a genuine differentiator, not just a marketing claim.

The site is at imgtoolkit.com β€” 100 tools, all free, all client-side. Happy to answer questions about any part of the implementation.

── more in #ai-tools 4 stories Β· sorted by recency
── more on @imgtoolkit 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain β€” perfect for shipping the agent you just read about.

$git push zahid main
β†’ Live at https://your-agent.zahid.host βœ“
Get free account β†’ Pricing
from €0/mo Β· no card required
LIVE [news/how-i-built-100-brow…] indexed:0 read:3min 2026-05-25 Β· β€”