DEV Community

Cover image for I Built 150 Free Browser Tools With No Backend — Here's the Stack
莊益宸
莊益宸

Posted on

I Built 150 Free Browser Tools With No Backend — Here's the Stack

TL;DR:

  • 4 months of weekends
  • 150 tools across 3 languages (English / 繁中 / 简中)
  • Zero backend. Zero database. Zero file uploads.
  • Pure Astro + vanilla JS, deployed to Cloudflare
  • Live: toolboxhub.info

I'm not here to sell you anything. No premium tier coming. No email gate. No "sign up for early access" funnel.

Just a tools site. Built because I wanted to.

If you're considering building a static site at this scale, here's what worked, what didn't, and what I wish I knew earlier.

The "Why" (kept short)

I kept needing tools — a PDF merger, a JSON formatter, a QR code generator. Every site I tried either:

  • Wanted my email
  • Uploaded my files to their server
  • Was buried under 6 ads
  • All of the above

So I built my own. Then kept building.

The Stack

Framework:  Astro 6 (static generation)
UI:         Vanilla CSS, no framework
Hosting:    Cloudflare CDN
Backend:    None
Database:   None
i18n:       TypeScript-based, hand-rolled
Bundle:     ~5KB JS first paint, libs lazy-loaded
Enter fullscreen mode Exit fullscreen mode

No React. No Vue. No backend. No login system.

Why Astro?

Three reasons:

  1. Zero-JS by default — Static HTML ships first, JS only loads when needed
  2. Per-page islands — A heavy tool (pdf-lib at 800KB) doesn't bloat the other 149 pages
  3. MPA model — Each tool is its own page, perfect for SEO

What's actually hard

1. Lazy-loading heavy libs

You can't just import 'pdf-lib' — it's 800KB. So I lazy-load from CDN only when a tool is opened:

const loadPdfLib = async () => {
  if (window.PDFLib) return window.PDFLib;
  const script = document.createElement('script');
  script.src = 'https://cdn.jsdelivr.net/npm/pdf-lib@1.17.1/dist/pdf-lib.min.js';
  document.head.appendChild(script);
  await new Promise(r => (script.onload = r));
  return window.PDFLib;
};
Enter fullscreen mode Exit fullscreen mode

Same pattern for tesseract.js (OCR), jsZIP, jsQR, lunar-javascript. ~12 libs total, all lazy.

2. The encoding trap

Halfway through, a friend opened the site on iOS Safari and saw ???? characters in some Chinese tool descriptions.

Cause: A .ts file got saved in wrong encoding during a copy-paste. ???? is valid ASCII, so:

  • Build passed
  • TypeScript compiled
  • SHA256 checks passed
  • Schema validation passed
  • Nothing flagged it

Fix: Added a build-time script that scans all output for ???? and fails the build.

Lesson: automated checks can't catch what a human eye sees.

3. The trailingSlash bug I shipped for 4 months

I had trailingSlash: 'never' in astro.config.mjs, but Apache's DirectorySlash On was redirecting /tools/foo/tools/foo/. Result: hreflang URLs said one thing, server said another. Google saw duplicate URLs for every page.

Took 4 months for someone (not me) to notice.

Now: trailingSlash: 'always', internal links all end in /, file URLs (llms-full.txt, sitemap-index.xml) don't. Clean.

4. i18n at scale

Some tools (like Taiwan invoice lottery, lunar calendar) make no sense in English. So:

export const TOOLS_BY_LOCALE: Record<Locale, ToolId[]> = {
  'en':    [...globalTools],
  'zh-tw': [...globalTools, ...taiwanSpecificTools],
  'zh-cn': [...globalTools],
};
Enter fullscreen mode Exit fullscreen mode

/en/tools/mars-text/ is a clean 404, not a half-translated tool page.

5. Per-tool OG images at build time

Started with one shared og-default.svg. Social shares looked generic.

Wrote a Node script that generates ~400 per-tool SVGs at build time:

// scripts/generate-og-images.ts
for (const locale of LOCALES) {
  for (const toolId of TOOLS_BY_LOCALE[locale]) {
    const tool = i18n[locale].tools[toolId];
    writeFileSync(
      `public/og/tools/${locale}/${toolId}.svg`,
      renderOgSvg({ name: tool.name, lang: locale })
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

Runs in 8 seconds. ~400 unique cards. Zero ongoing maintenance.

6. llms.txt for AI crawlers

GPTBot, ClaudeBot, PerplexityBot all read llms.txt. Mine is auto-generated from the same TOOLS_BY_LOCALE source of truth at build time. Stays in sync forever.

What surprised me

Open data is everywhere. Taiwan postal codes, fuel prices, transport schedules — all CSV downloads or free APIs. Hardest part was deciding what NOT to add.

lunar-javascript is incredible. For Chinese calendar conversion (1900–2100, 干支, 黃道吉日), a 50KB library handles it all.

@cantoo/pdf-lib saved my PDF encryption tool. Original pdf-lib doesn't support encryption. Found out the hard way after shipping a broken tool.

What still doesn't work

  • Monetization without ruining UX — still figuring this out
  • Cold-start SEO is brutal. 4 months in, organic clicks are still under 100/day
  • Discovery is hard. Nobody searches "free tools." They search "PDF merger" and find established sites with better DR

The honest part

I built 150 tools. I made $0. I have maybe 10 daily users.

(Also: yes, AI helped me ship this. Claude for debugging, ChatGPT for i18n translations, and this very post got a proofread pass. The bugs were 100% organic though.)

But that was never the point. I wanted to make useful things and not charge for them. Shipping 150 working tools across 3 locales taught me more about static site architecture, i18n, browser APIs, and the limits of "no backend" than any tutorial could.

If you're considering a similar project: do it as a learning exercise, not as a business. If it turns into a business, bonus.

Try it

  • Live: toolboxhub.info
  • A few that show off the architecture:
    • PDF Merge — drag-reorder pages, browser-only
    • AI Token Counter — counts tokens for OpenAI / Claude / Gemini
    • Image Converter — JPG/PNG/WebP/AVIF/BMP/GIF in 6 directions, client-side

Happy to answer technical questions in the comments — especially on Astro patterns, lazy-loading heavy libs at scale, or i18n with locale filtering.

What would you have built differently?

Top comments (0)