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
No React. No Vue. No backend. No login system.
Why Astro?
Three reasons:
- Zero-JS by default — Static HTML ships first, JS only loads when needed
- Per-page islands — A heavy tool (pdf-lib at 800KB) doesn't bloat the other 149 pages
- 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;
};
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],
};
/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 })
);
}
}
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?













