I got tired of the same routine.
I'd need to convert one HEIC photo, or check a file's EXIF data, or work out a loan payment. I'd land on some converter site, upload the file to a stranger's server, wait, download the result, and then wonder where that file actually went.
It bugged me. Most of these jobs don't need a server at all.
So I built verystep.com — a growing collection of free utilities that run entirely in the browser. No backend doing the work. No database. No sign-ups. Your files and inputs stay on your device because, well, there's nowhere for them to go.
This post is the making-of. The choices, the trade-offs, and a few things I got wrong first.
The one rule I started with
If a job can be done client-side, it must be done client-side.
That sounds simple. It gets strict fast.
It means no uploading images to convert them. No sending pasted text to an API to count the words. No storing calculator inputs "for your convenience."
It also means I have to be honest about the exceptions. Two tools fetch public data — weather and currency rates — and they call keyless public APIs directly from your browser. That call is disclosed in the privacy policy. Nothing you type goes along for the ride.
Everything else? Local. Close the tab and it's gone.
The boring architecture (that's the point)
The server side is deliberately dull.
One small Flask app serves the pages. Each tool lives at its own path, like /heic/ or /compound/, with its own plain JavaScript doing the actual work on the page. There's no shared user system because there are no users. No sessions. No uploads folder filling up with other people's photos at 3 a.m.
I like dull here.
Dull means there's less to leak, less to patch, and less to pay for. It also means the privacy story isn't a promise I have to keep — it's a property of how the thing is built. I can't hand over data I never received.
Example 1: Converting HEIC photos without an upload
The HEIC converter is a good test of the rule. iPhone photos are HEIC, half the world can't open HEIC, so people upload them to random sites every day.
I didn't want to be one of those sites.
The page uses the open-source heic2any library, self-hosted on my own domain instead of loaded from a third-party CDN. The conversion happens in a blob, in your tab:
import heic2any from "./vendor/heic2any.js";
const out = await heic2any({ blob: file, toType: "image/jpeg", quality: 0.92 });
const url = URL.createObjectURL(out);
downloadLink.href = url; // the file never left the device
Self-hosting the library mattered to me. If the script comes from someone else's CDN, I've outsourced part of my privacy claim. Self-host it, pin the version, and the page you load is the page I checked.
Short version: pick a file, get a JPG. Nothing crosses the wire.
Example 2: Putting state in the URL, not in a database
Calculators raised a different problem. People want to share results.
"Here's the loan breakdown I meant" is a normal thing to send a partner or a colleague. If I don't have accounts or saved records, how does sharing work?
The URL does it.
For the main calculators, the inputs are encoded in the query string. Change a value and the link updates. Send the link and the other person lands on the same inputs and the same result, recomputed live on their machine:
// write state
const params = new URLSearchParams({ principal, rate, years, monthly });
history.replaceState(null, "", "?" + params.toString());
// read state on load
const q = new URLSearchParams(location.search);
if (q.has("principal")) principalInput.value = q.get("principal");
No short-link service. No ID to look up. The link is the record.
There's a side benefit I didn't expect: debugging got easier. When someone reports a weird result, they can just paste the link. I see exactly what they saw.
Example 3: Checking calculator maths like it matters (because it does)
A percentage widget can be sloppy and nobody gets hurt. A mortgage or take-home-pay calculator can't.
So I don't trust my own JavaScript just because it runs.
For each finance calculator, I built an independent mirror of the formula — separate code, written from the source definitions — and ran known inputs through both. They have to agree to the cent before a tool ships. If they disagree, one of them is wrong, and I find out which.
Sounds slow. It is, a bit.
But it caught real bugs. Rounding in the wrong place. A tax cap applied a step too early. The kind of error that still looks right on the page, which is exactly why it's dangerous.
My rule since then: a plausible-looking number is not a verified number.
Even the AI tool stays on the device
The newest tool is a background remover, and it was the hardest test of the whole idea.
It runs an open-source model (BiRefNet-lite) through ONNX Runtime Web, right in the browser — WebGPU where that's available, WASM as the fallback. The model file is self-hosted, and the page says plainly that the first use downloads it, because a big download you didn't agree to is its own kind of rude.
Your photo still isn't uploaded. The model comes to the photo, not the other way round.
That one taught me the most about real hardware, by the way. Emulators and headless tests said "fine." Real phones said otherwise. I test on actual devices now before I believe anything works.
What this design costs me
I won't pretend it's all upside.
First loads can be heavy. Libraries and models ship to the browser. I lazy-load what's tool-specific and cache aggressively, but there's no hiding physics.
Devices vary wildly. A conversion that's instant on a desktop can crawl on an old phone. I have to design for the slow case, not the demo case.
No sync, ever. There's nothing to sync to. For single-purpose tools I think that's fine — nobody needs their coin flip history in the cloud — but it's a real limit.
Some jobs just don't fit. If a tool genuinely needs fresh server-side data or heavy shared state, it doesn't belong in this collection. I say no to those instead of bolting on a backend for one page.
Those limits are the price. I pay them on purpose.
What I'd tell someone building this way
Keep the rule simple enough that you can't negotiate with yourself. Mine fits in one line, and every new tool has to pass it.
Verify numbers against a second implementation. Browsers are forgiving; arithmetic shouldn't be.
And test on the hardware your visitors actually own. Not once. Every time it matters.
New tools get added regularly, and each one has to earn its place the same way — useful on its own, honest about what it does, and quiet about you.
Have a look, tell me what breaks
The toolbox lives at verystep.com. It's free, there's nothing to sign up for, and if a tool saves you an upload to a site you don't trust, it did its job.
I'd genuinely like feedback — especially the unkind kind. Which tool felt slow? Which result looked off? What small utility do you still have to upload a file to do, and wish you didn't?
Drop it in the comments. I read all of them, and the bug reports get fixed first.
— William Sun, Lemon Salad Studio
Top comments (0)