I shipped a toolkit of 150 small browser tools. Every one of them runs in the tab: the file is read locally, processed locally, and never sent to a server.
That's not a marketing line — it's an architectural constraint I imposed on myself, and it made almost every tool harder to build. Here's what it costs and how the pieces actually work.
What the 150 tools are
Roughly four groups, and each one needed a different processing model:
- Text and dev utilities (the majority). JSON/YAML/XML, Base64, JWT, regex, hashes, cron parsing, SQL formatting, diff, CSV ↔ JSON. Pure JS, no wasm, instant. These are easy and they're not why anyone stays.
-
Image tools. Compress, resize, crop, convert, watermark, collage, OCR. Canvas +
OffscreenCanvasin Web Workers, so the main thread keeps painting at 60 fps while a 12 MP photo is being re-encoded. -
PDF tools. Merge, split, page ranges, watermark, PDF → images, images → PDF.
pdf-lib, entirely in memory. -
Audio and video. Trim, video → GIF, extract audio, screenshots, screen/camera/mic recording.
ffmpeg.wasmin a worker, single-threaded — full write-up of why in a separate post.
Why static export, and why it's a feature
The whole site is a Next.js static export on Cloudflare Pages: HTML pages, no server process.
The performance reason is boring (CDN cache). The privacy reason is the real one: there is no server to send a file to. A tool can't accidentally upload your PDF if the deployment has no runtime that accepts a PDF. That's a property I can't lose by shipping a careless commit, and it's worth more than any privacy policy paragraph.
The cost is that everything client-side has to fit in a static bundle, which is where the 25 MiB per-asset limit and the wasm-from-CDN compromise come from.
The data flow, for every tool
File → <input type=file> → ArrayBuffer (in the tab)
→ wasm / worker / canvas
→ Blob → URL.createObjectURL → <a download>
There is no fifth step. If you open the Network panel and process a 40 MB video, the only requests you'll see are the page, the JS, and the wasm engine on first use. That's the whole test, and it's why I wrote a separate post on how to audit claims like this one.
Four things this architecture cost me
1. No sync, no history across devices, no cloud backup. No account means no place to put them. Some users ask for it; the honest answer is that adding it would delete the only thing that makes this different.
2. Memory ceilings are real and user-visible. A very large video can kill a tab. On phones it's worse. I try to fail with a message rather than a blank screen, and I don't always succeed.
3. The first video use is a 30 MB download. Unavoidable given the asset limit, so the UI shows a progress number instead of a spinner and explains what's downloading.
4. Bundle weight on a 150-tool site. Every tool page lazy-loads its own chunk, which mostly works, and the entry bundle is small. It's not a small site, and pretending otherwise would be silly.
What's still rough
- The homepage is a wall of 150 tools. I know that's bad IA. Category pages help; the homepage still isn't good.
- Mobile works for text and image tools; the video tools are desktop-first and I'd rather say so than ship a spinner.
- 16 languages is a maintenance tax. Every new tool is 16 translations before it's usable, and machine-translated UI copy for technical terms is a permanent quality problem.
The question I actually want answered
If you had to delete 100 of these 150, which 50 would you keep?
I'm not being cute. The long tail of "tool #151" costs me more in maintenance than it returns, and I'd rather make a handful genuinely good than keep widening a grid.
Start here if you want to try one: image compressor — no upload, no signup. Full list at wtoolskit.com. It installs as a PWA, so it also works with Wi-Fi off.
Top comments (0)