I built 342 browser-native dev tools – here’s why everything runs client-side

I built 342 browser-native dev tools — here’s why everything runs client-side

Over the last few months I quietly built ZeroServer.tools — a suite of 342 free developer utilities. JSON formatter, Base64 encoder, hashing, JWT decoder, regex tester, image compressor, PDF tools, CSS generators, calculators, converters, and a lot more.

But the design constraint I imposed from day one is what made this interesting to build: every single tool runs 100% in your browser. No backend. No server calls. Nothing you type ever leaves your device.

Here’s why I made that choice — and how the architecture works.

The problem I kept running into

Whenever I needed a quick tool — format this JSON, encode this string, generate a hash — I’d find a site, paste my data in, and get my result.

But I’d always wonder: where did my data go? Most of these tools have a backend. Your JSON goes to their server. Your JWT (with its payload claims) gets decoded server-side. Your password gets “strength-checked” by someone else’s API.

Most sites are fine. But I was using them for work — sometimes with API keys, sometimes with internal JSON structures I probably shouldn’t paste into random websites.

So I built one where that problem doesn’t exist.

The architecture: Next.js static export on Cloudflare Pages

The entire site is a Next.js static export (output: "export" in next.config.ts). No server-side rendering, no API routes, no Edge Functions. Just static HTML + JS files that Cloudflare serves from the edge.

// next.config.ts
const nextConfig: NextConfig = {
  output: "export",
  trailingSlash: true,
};

This has a real constraint: you can’t do anything server-side. No fs, no fetch at build-time for dynamic data, no crypto from Node. Everything that runs at request time has to be browser JavaScript.

That constraint turned out to be the right forcing function.

How browser-native APIs cover 95% of what you need

I expected to write a lot of polyfills. I didn’t. The browser has gotten remarkably capable:

Cryptography — Web Crypto API

// SHA-256 hash of any string
const buf = await crypto.subtle.digest(
  "SHA-256",
  new TextEncoder().encode(input)
);
const hex = Array.from(new Uint8Array(buf))
  .map(b => b.toString(16).padStart(2, "0"))
  .join("");

No Node crypto module needed. SHA-1, SHA-256, SHA-384, SHA-512 are all built into crypto.subtle. HMAC, PBKDF2, AES-GCM — all there.

File operations — FileReader + Canvas API
The image tools (compressor, resizer, format converter, watermark, EXIF stripper) all use FileReader to read files and HTMLCanvasElement to process them. The PDF tools use pdf-lib compiled to WebAssembly — runs entirely in the browser.

// Image compression — client-side only
const img = new Image();
img.onload = () => {
  const canvas = document.createElement("canvas");
  canvas.width = img.width;
  canvas.height = img.height;
  const ctx = canvas.getContext("2d")!;
  ctx.drawImage(img, 0, 0);
  canvas.toBlob((blob) => {
    // blob is the compressed image — never touched a server
  }, "image/webp", quality);
};
img.src = URL.createObjectURL(file);

Random/UUID — crypto.randomUUID()

const id = crypto.randomUUID(); // cryptographically random, browser-native

The SSR problem — and how I solved it

Next.js does server-side rendering at build time (for static export). This means your component renders once in Node.js at build time, then hydrates in the browser.

Problem: anything that uses window, document, FileReader, Date.now(), or Math.random() will crash the build or produce hydration mismatches.

The solution I settled on:

  1. Pure logic in useMemo — formatting, parsing, encoding, converting. These are deterministic: same input → same output. No SSR problems.

  2. Side effects in useEffect — anything that touches the DOM, reads files, or uses browser-only APIs. This runs client-side only.

  3. Seeded PRNGs for “random” output — for tools like the Lorem Ipsum generator and quote picker, I use a deterministic seeded PRNG so the SSR output matches the client render:

function prng(seed: number): number {
  return Math.abs(Math.sin(seed + 1.618) * 1e8) % 1;
}

Math.sin with a fixed seed is pure — same result in Node and browser. No hydration mismatch.

  1. new Date(string) is safe, new Date() is notnew Date() returns the current time, which differs between build-time Node and client runtime. new Date("2025-01-01T00:00:00Z") is deterministic and safe anywhere.

The registry-driven architecture

All 342 tools share a single source of truth: lib/tools.ts. Every nav item, dashboard card, search result, sitemap entry, and SEO metadata is derived from this registry. Adding a new tool is:

  1. Create app/<slug>/layout.tsx (SEO metadata)
  2. Create app/<slug>/page.tsx (the tool)
  3. Add one entry to lib/tools.ts
  4. Add chain links to lib/toolChains.ts (the “try also” suggestions)

No config files, no separate sitemap file to maintain, no duplicate metadata.

What’s in the stack

  • Next.js 16 with App Router, static export
  • React 19
  • Tailwind CSS v4 (JIT, no config file)
  • Lucide React for icons (one icon per tool)
  • Cloudflare Pages for hosting (generous free tier, global CDN)
  • No database, no auth, no backend

The tools people use most

Based on the categories that get the most traffic from search:

  1. JSON Formatter — the classic, still drives a lot of traffic
  2. Image Compressor — “files never leave your browser” resonates for images
  3. PDF tools — merge, split, rotate, all client-side (pdf-lib/WASM)
  4. Hash generators — SHA-256, MD5, HMAC
  5. Base64 — evergreen for devs

The privacy angle matters most for crypto/hashing and image/PDF tools, where people are most sensitive about where their files go.

Try it

zeroserver.tools — 342 tools, 100% client-side, free, no signup.

I’d genuinely love feedback — especially:

  • Which tools feel off or inaccurate?
  • What’s missing that you reach for regularly?
  • Does the “nothing leaves your browser” claim come through clearly?

I’m building this in the open and taking requests.