Tutorials

How this site serves 2,119 tools from one file

No framework, no build step, no server. What that buys, what it costs, and the three techniques that make it hold together.

The constraint

This whole site is one HTML file. The styling is in it, the code is in it, all 2,119 tools are in it. There is no framework, no bundler, no package manager and no server. You can save the page to a USB stick and every tool still works.

That was a decision rather than an accident, and it is worth setting out what it buys before explaining how it works.

  • It works offline. Once the page is loaded, everything is already there.
  • It cannot rot. There is no dependency to go unmaintained, no build that stops reproducing in two years, no lockfile to resolve.
  • The privacy claim is structural. With no server there is nowhere to send your data, so “nothing is uploaded” is a fact about the architecture rather than a promise about conduct.
  • It deploys by copying a file. Any static host will do.

The costs are real too: no server means no AI features, no accounts and no saved work, and one file means the browser parses all of it up front.

1. Generate tools from tables, not from copies

498 hand-written tools would be unmaintainable. But most of the catalogue is not 498 distinct ideas — it is a handful of shapes with a lot of parameters. “Resize to a YouTube thumbnail” and “resize to a TikTok cover” are the same twenty lines with different numbers.

So the sizes live in a table, and one loop turns each row into a registered tool with its own id, its own description and its own implementation. The character-limit counters work the same way, as do the calculators and the unit conversions. Correcting a platform size is a one-line change to data, and the tool, its description and its sitemap entry all update together because they are all read from the same row.

The line this must not cross A generated entry has to be a genuinely different job. Two placement sizes give two different correct answers, so they are two tools. “PNG to JPG” and “PNG to WebP” are one converter with a dropdown, and listing them separately is how a catalogue of thirteen gets advertised as six hundred. Every table here varies a real parameter — a dimension, a cap, a factor — never just the title.

2. Give every tool its own address

A single-page app with one URL is invisible to search engines and impossible to bookmark. The fix is small: opening a tool pushes ?t=word-counter into the address bar with history.pushState, and rewrites the document title and meta description to match. Arriving on that URL opens the tool directly.

That gives every tool a shareable link, a working back button, and a distinct entry in the sitemap — which is itself generated from the same catalogue, so it can never list a tool that does not exist.

3. Sort by usefulness, and cap what you render

Rendering 498 cards at once is 498 hover-shadow layers and a visibly janky scroll on a phone, so the grid stops at 120 and search reaches the rest. That cap creates a second problem: in a category of 343 unit conversions, whatever sorts last is unreachable by browsing.

The answer is to rank rather than to raise the cap. Unit pairs are scored by how everyday both units are, so “kg to lb” and “MB to GB” are near the front and “nautical mile to yard” is not. Ranking by table instead — the obvious first attempt — put all 72 length pairs ahead of every weight pair, which is exactly backwards.

What the browser already gives you

A surprising amount of what these tools need is built in: crypto.getRandomValues and crypto.subtle.digest for passwords and hashes, canvas for every image operation, Intl for dates and number formatting in any locale, FileReader for reading a file without uploading it. Reaching for a library is often reaching past something already in the platform.

Would I do it this way again?

For this, yes. The single-file constraint is a good fit for a catalogue of small independent tools that share a shell, and it makes the honest privacy position possible.

It would be the wrong choice for anything with accounts, persistence or collaboration — those need a server, and pretending otherwise leads to storing things in local storage that have no business being there. The architecture should follow from what the thing actually does, which in this case is: take some input, compute something, give it back, forget it.