I built 62 image tools that never upload your photos. Here's what was harder than expected.
I do a lot of SEO and web work, which means I touch images every single day. Compress this one for a page, remove the background from that one, convert a HEIC someone sent me, resize something for a blog post. And every
I do a lot of SEO and web work, which means I touch images every single day. Compress this one for a page, remove the background from that one, convert a HEIC someone sent me, resize something for a blog post.
And every day it was the same routine. One site for compressing, another for background removal, and another for converting. Half of them hit me with "upgrade to Pro" after one or two images. The rest were so full of ads I had to hunt for the actual download button. Some wanted me to sign up just to get my own file back. And all of them wanted me to upload the image to their server first, for something my laptop can easily do on its own.
At some point I got tired of it and started building my own. That became ImgKit: a set of image tools (compress, resize, HEIC to JPG, remove background, passport photos, that kind of stuff) with no upgrade wall, no ads, no account, and nothing uploaded. Everything runs in your browser tab. It's at 62 tools now, which is more than I planned, and I built most of it with Claude Code.
This post isn't an ad. It's what I learned building it, because a lot of it surprised me.
The setup is boring on purpose.
It's a Next.js static export on Vercel. No backend, no database, no API routes. That was the whole point: if there's no server, there's nowhere for your photo to go. It also means hosting costs me basically nothing.
The tradeoff is that everything has to work in the browser, including stuff you'd normally throw at a server.
"Compress to exactly 50 KB" sounds easy. It isn't.
This was the feature I thought would take an afternoon.
Canvas has toBlob(type, quality), so you just lower the quality until the file fits, right? Except quality doesn't map to file size in any predictable way. The same quality setting gives you 30 KB on one photo and 400 KB on another.
What I ended up with is a binary search over quality. Encode, check the size, go up or down, repeat. Seven steps gets you within about 1% of the best quality that still fits. And sometimes no quality fits, because the photo is just too big in pixels. In that case it scales the image down a bit and searches again.
Real example from my own testing: an 858 KB waterfall photo, target 50 KB. It came out at 43.2 KB, quality 17%, resized from 1600×1067 to 1372×915. Users see that note on the result, so nobody gets surprised that their image got smaller in pixels too.
The other thing: canvas.toBlob is not a great JPEG encoder. I switched the heavy lifting to WebAssembly builds of mozjpeg, oxipng and an AVIF encoder (the jSquash packages). Same quality, noticeably smaller files.
Running AI models in a browser tab
This was the fun part, and the part with the most surprises.
Background removal, upscaling, object removal and OCR all run locally with transformers.js and onnxruntime-web. The model downloads once, gets cached, and after that it works offline. Some numbers from testing:
- Background removal uses MODNet, about 13 MB. First run includes the download. After that it's a few seconds on a normal laptop.
- Object removal uses MI-GAN, 28 MB, and it's honestly fast. Under a second.
- The upscaler is the slow one. On a laptop without a usable GPU it can take a minute, because it's all on the CPU. I had to cap input sizes and process in tiles so it doesn't freeze the tab.
Two things I didn't see coming:
WASM is single threaded for me. Multi-threaded WebAssembly needs special headers (COOP/COEP), and those break a bunch of other things on a static site. So every AI model runs on one thread unless the browser has WebGPU. That's the main reason some tools feel fast on one machine and slow on another.
Library versions matter a lot. I had to pin transformers.js to 3.7.5. The newer version's onnxruntime build just crashed for my models. No useful error, it just died.
For finding text in photos (for the "remove text from image" tool), I first tried Tesseract. It was bad at it. It missed white text on a light wall and "found" words in bicycle spokes. Switching to PaddleOCR's text detector (4.7 MB) fixed it.
The HEIC bug I didn't know I had
iPhones save photos as HEIC, which Windows still can't open by default. HEIC to JPG is one of the most used tools.
I was using a popular library called heic2any. Last week a security scanner flagged my site because my Content Security Policy allowed unsafe-eval. I dug in, and that permission was only there because heic2any compiles code at runtime. So I swapped it for heic-to, which has a build that doesn't need eval.
Then I compared the output of both, just to make sure nothing changed. Something did change. The old library had been making photos darker the whole time. Shadows crushed, reds too strong.
I measured how close each output was to the original photo (PSNR, higher is better):
| Photo | Old library | New library |
|---|---|---|
| Waterfall | 27.0 dB | 36.9 dB |
| Portrait | 26.7 dB | 42.7 dB |
| Another photo | 27.2 dB | 38.7 dB |
So a security fix ended up being a quality fix too. Nobody had complained, which probably just means people assumed that's how HEIC conversion looks.
While I was at it, I found one more Function(...) call that would've broken under a strict CSP. It was a feature check inside a Node util polyfill that JSZip pulls in. Using JSZip's prebuilt browser bundle instead got rid of it, and now JSZip only loads when someone actually downloads a ZIP.
The mistakes that were invisible
AI-assisted coding got features working fast. The things that bit me were the ones you can't see by clicking around.
I spammed Bing without knowing it. I set up IndexNow, which tells Bing when a page changes. My site has 113 pages. Bing's report showed 4,600 submissions. The sidebar lists every tool, so every time I added one tool, every page's content "changed", and the script resubmitted all of them. Same with my sitemap's lastmod dates: I was telling search engines 100 pages updated when really 2 did. The fix was simple once I saw it: ignore the header, nav, sidebar and footer when deciding whether a page changed.
Backlinks are mostly fake. Ahrefs showed 359 sites linking to me after a month. About 5 are real. The rest are domains like link-baron-syndicate-equity.store. That's apparently normal for new sites, but it was funny to see.
Bing still has 0 of my pages indexed. Google started showing the site within days. Bing lists my pages as "discovered, not crawled". Someone on X told me that's crawl priority for a new domain, and the fix is real links, not more pings. I think they're right.
Was it worth doing it all in the browser?
For this kind of tool, yes. The image is already on your device. Uploading it somewhere just to shrink it never made much sense to me.
It's not free though. You're limited by the user's hardware, big models need a download, and you can't fix a slow CPU from your side. If I needed collaboration or storage, I'd want a server.
You can check the privacy claim yourself, by the way: open DevTools, go to the Network tab, run a tool, and watch. Nothing goes out except the model downloads. Or just turn off your Wi-Fi after the page loads.
What's next
Honestly, less building and more talking to people. I built 62 tools and then realized I don't know which 5 people actually need. If you try it and something's missing or broken, I'd really like to hear it.
It's free, no account: imgkit.xyz
If you've run ML models in the browser, I'm curious what your experience was with WebGPU vs plain WASM. That's the next thing I want to figure out.
Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.