{"slug": "how-i-built-100-browser-based-image-tools-with-no-server-ffmpeg-wasm-pdf-lib-ai", "title": "How I Built 100 Browser-Based Image Tools With No Server (FFmpeg WASM, PDF-lib, AI Background Removal)", "summary": "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.", "body_md": "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.\n\nI wanted to build something where everything runs in the browser. Your files never leave your device. No server, no account, no paywall.\n\nThis is the technical breakdown of how I got 100 tools working entirely client-side.\n\nThe core idea: the browser is powerful enough\n\nModern 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.\n\nHere's the stack I settled on:\n\nReact + Vite — fast builds, lazy-loaded routes so each tool only loads what it needs\n\nCanvas API — handles 80% of image operations (resize, crop, rotate, watermark, convert formats)\n\npdf-lib — pure JS PDF manipulation (merge, split, compress, add pages)\n\npdfjs-dist — PDF rendering to canvas (for PDF to JPG conversion)\n\nFFmpeg WASM — video processing in the browser\n\n[@imgly](https://dev.to/imgly)/background-removal — AI background removal using ONNX models\n\nTesseract.js — OCR, runs a full Tesseract engine via WASM\n\nbrowser-image-compression — handles the heavy lifting for image compression\n\nThe interesting challenges\n\nIt works. But there are gotchas:\n\nimport { FFmpeg } from \"@ffmpeg/ffmpeg\";\n\nimport { fetchFile, toBlobURL } from \"@ffmpeg/util\";\n\nconst ffmpeg = new FFmpeg();\n\nawait ffmpeg.load({\n\ncoreURL: await toBlobURL(`/ffmpeg-core.js`\n\n, \"text/javascript\"),\n\nwasmURL: await toBlobURL(`/ffmpeg-core.wasm`\n\n, \"application/wasm\"),\n\n});\n\nawait ffmpeg.writeFile(\"input.mp4\", await fetchFile(file));\n\nawait ffmpeg.exec([\"-i\", \"input.mp4\", \"-q:a\", \"0\", \"-map\", \"a\", \"output.mp3\"]);\n\nconst data = await ffmpeg.readFile(\"output.mp3\");\n\nThe 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.\n\nDynamic import on the FFmpeg tools was essential — you don't want 30MB loading on the homepage.\n\nimport { removeBackground } from \"[@imgly](https://dev.to/imgly)/background-removal\";\n\nconst blob = await removeBackground(imageFile, {\n\npublicPath: \"[https://cdn.imgly.com/background-removal/..](https://cdn.imgly.com/background-removal/..).\",\n\nmodel: \"medium\",\n\n});\n\nThe 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.\n\nThe 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.\n\nimport { PDFDocument } from \"pdf-lib\";\n\nconst mergedPdf = await PDFDocument.create();\n\nfor (const file of files) {\n\nconst bytes = await file.arrayBuffer();\n\nconst pdf = await PDFDocument.load(bytes);\n\nconst pages = await mergedPdf.copyPages(pdf, pdf.getPageIndices());\n\npages.forEach(p => mergedPdf.addPage(p));\n\n}\n\nconst merged = await mergedPdf.save();\n\nFor \"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.\n\nconst RemoveBackground = lazy(() => import(\"@/pages/remove-background\"));\n\nconst FfmpegVideoToMp3 = lazy(() => import(\"@/pages/video-to-mp3\"));\n\nHeavy 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.\n\nInitial page load is under 100KB of JS. A user who only compresses images never downloads any FFmpeg or ONNX code.\n\nctx.filter = `blur(${blurStrength}px)`\n\n;\n\nctx.drawImage(canvas, x, y, w, h, x, y, w, h);\n\nctx.filter = \"none\";\n\nWorks surprisingly well on photos with 1–4 faces. Degrades on crowds — but so does every commercial API at that task.\n\nWhat I learned\n\nThe 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.\n\nLazy loading is non-negotiable. Without it, you're shipping 50MB of JS to every visitor regardless of which tool they use.\n\nHeaders matter for WASM. SharedArrayBuffer requires COOP: same-origin and COEP: require-corp. Get these wrong and FFmpeg silently fails.\n\nClient-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.\n\nThe site is at [imgtoolkit.com](https://imgtoolkit.com/) — 100 tools, all free, all client-side. Happy to answer questions about any part of the implementation.", "url": "https://wpnews.pro/news/how-i-built-100-browser-based-image-tools-with-no-server-ffmpeg-wasm-pdf-lib-ai", "canonical_source": "https://dev.to/imgtoolkit/how-i-built-100-browser-based-image-tools-with-no-server-ffmpeg-wasm-pdf-lib-ai-background-5838", "published_at": "2026-05-25 21:36:57+00:00", "updated_at": "2026-05-25 22:03:36.364622+00:00", "lang": "en", "topics": ["ai-tools", "ai-products", "computer-vision", "artificial-intelligence", "generative-ai"], "entities": ["ImgToolkit", "FFmpeg WASM", "pdf-lib", "Tesseract.js", "Canvas API", "React", "Vite", "ONNX"], "alternates": {"html": "https://wpnews.pro/news/how-i-built-100-browser-based-image-tools-with-no-server-ffmpeg-wasm-pdf-lib-ai", "markdown": "https://wpnews.pro/news/how-i-built-100-browser-based-image-tools-with-no-server-ffmpeg-wasm-pdf-lib-ai.md", "text": "https://wpnews.pro/news/how-i-built-100-browser-based-image-tools-with-no-server-ffmpeg-wasm-pdf-lib-ai.txt", "jsonld": "https://wpnews.pro/news/how-i-built-100-browser-based-image-tools-with-no-server-ffmpeg-wasm-pdf-lib-ai.jsonld"}}