DEV Community

Impossibru
Impossibru

Posted on

Zero backend, 6 languages, one repo: the architecture of a browser-only tools site

I run a little site called Webtoolio (https://webtoolio.org). Image compressor, unit converters, password generator, JSON formatter, that kind of thing. 48 tools at last count, all that without a server. Not "serverless functions", I mean nothing. The whole site is static files, and every tool does its work in your browser. All hosted on CF.

I mostly built this to see what astro can do, around that I wanted to also see how far I can take some of these tools and the ergonomics of using wasm for heavier workloads like image compression or parsing json.

Some things I learned building it.

Image compression in the browser actually works now

This was the part I expected to be a compromise, and it just... isn't. jSquash gives you the same codecs everyone uses anyway (mozjpeg, oxipng, webp) compiled to WASM. They run in a worker, the UI stays responsive, and a 10MB photo compresses in a second or two without going anywhere.

The encoders are about 350KB, which I refuse to make people download on page load, so they only load the first time someone actually hits the compress button. Most visitors never do. The one thing that bit me: Vite tries to pre-bundle WASM packages and mangles them, you have to put them in optimizeDeps.exclude and then it's fine.

Render the answer before the JavaScript arrives

Every tool page has a default input, which means the default output is knowable at build time. So I compute it there. The hash page ships with the hashes already in the HTML, the formatter pages ship formatted. Astro makes this almost free: the frontmatter imports the exact same logic package the client script uses, runs it at build, done.

I originally did this for looks, and then discovered it's what keeps layout shift at zero. My test suite fails the build if CLS goes over 0.02, and every time it's fired, the cause was a client script filling in something the server had left blank. At this point I treat that test as a design rule: if the client draws it on load, the server should have drawn it first.

The budget that keeps the site fast

One number does a lot of work for me: if a page's JS chunk goes past ~50KB, the heavy library moves behind a dynamic import. The trick is to start the import immediately but await it lazily:

const ready = import('sql-formatter'); // starts downloading now
button.onclick = async () => {
  const { format } = await ready; // almost always already resolved
  output.value = format(input.value);
};
Enter fullscreen mode Exit fullscreen mode

So the 256KB SQL formatter downloads in parallel while the page is already usable, and by the time a human has pasted their query and clicked, it's there. Nobody notices, which is the point.

Six languages was the actually hard part

English, Japanese, Portuguese, Spanish, German, French. Not because translation is hard, but because keeping six locales complete over dozens of tools is the something you will absolutely screw up by hand. My fix was to make the compiler do it: every UI string goes through a typed dictionary, and a missing key in any locale is a build error. Adding a tool without all six description files? Build error. It's crude and it works. I don't ship untranslated pages because I can't.

Was it worth it? The English tool-site market is brutally crowded. "JSON formatter" in Japanese or Portuguese, much less so. It will be interesting if all this work will yield some traffic from those locales.

The important tests

Unit tests on the logic were important, but the bugs users would actually hit were visual: the syntax-highlight overlay drifting a pixel out of alignment under the textarea, a flex container quietly not growing, console errors on one locale only. So there's a vitest browser-mode suite that runs against the built site and checks exactly those things on every page. It's saved me more times than the unit tests have.

Some Stats

  • 48 tools, 6 languages, 313 static pages
  • Full build: 1.7s (Bun + Astro)
  • 886 unit tests, 2,896 assertions, run in 1.5s
  • Median tool page ships 2.8KB of its own JS
  • Heaviest lazy chunk: 256KB SQL formatter, loads only when needed
  • Image codecs: ~350KB worker + WASM, loaded on first compress only
  • Typical page visit downloads under 100KB of JS
  • Hosting cost: $0

That's the tour. Free, no ads, no accounts, and I'm always looking for the next tool worth adding, so if something you use weekly is missing, tell me: https://webtoolio.org

Top comments (0)