<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: 莊益宸</title>
    <description>The latest articles on DEV Community by 莊益宸 (@_1511fe2bfdf0ecb2c92f0).</description>
    <link>https://dev.to/_1511fe2bfdf0ecb2c92f0</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3956904%2F4b9ae741-46e8-42c0-a856-995003b0f018.png</url>
      <title>DEV Community: 莊益宸</title>
      <link>https://dev.to/_1511fe2bfdf0ecb2c92f0</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/_1511fe2bfdf0ecb2c92f0"/>
    <language>en</language>
    <item>
      <title>I Built 150 Free Browser Tools With No Backend — Here's the Stack</title>
      <dc:creator>莊益宸</dc:creator>
      <pubDate>Thu, 28 May 2026 15:44:37 +0000</pubDate>
      <link>https://dev.to/_1511fe2bfdf0ecb2c92f0/i-built-150-free-browser-tools-with-no-backend-heres-the-stack-3639</link>
      <guid>https://dev.to/_1511fe2bfdf0ecb2c92f0/i-built-150-free-browser-tools-with-no-backend-heres-the-stack-3639</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;4 months of weekends&lt;/li&gt;
&lt;li&gt;150 tools across 3 languages (English / 繁中 / 简中)&lt;/li&gt;
&lt;li&gt;Zero backend. Zero database. Zero file uploads.&lt;/li&gt;
&lt;li&gt;Pure Astro + vanilla JS, deployed to Cloudflare&lt;/li&gt;
&lt;li&gt;Live: &lt;a href="https://toolboxhub.info" rel="noopener noreferrer"&gt;toolboxhub.info&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'm not here to sell you anything. No premium tier coming. No email gate. No "sign up for early access" funnel.&lt;/p&gt;

&lt;p&gt;Just a tools site. Built because I wanted to.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  The "Why" (kept short)
&lt;/h2&gt;

&lt;p&gt;I kept needing tools — a PDF merger, a JSON formatter, a QR code generator. Every site I tried either:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Wanted my email&lt;/li&gt;
&lt;li&gt;Uploaded my files to their server&lt;/li&gt;
&lt;li&gt;Was buried under 6 ads&lt;/li&gt;
&lt;li&gt;All of the above&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So I built my own. Then kept building.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Stack
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;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
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No React. No Vue. No backend. No login system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Astro?
&lt;/h2&gt;

&lt;p&gt;Three reasons:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Zero-JS by default&lt;/strong&gt; — Static HTML ships first, JS only loads when needed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Per-page islands&lt;/strong&gt; — A heavy tool (pdf-lib at 800KB) doesn't bloat the other 149 pages&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MPA model&lt;/strong&gt; — Each tool is its own page, perfect for SEO&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What's actually hard
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Lazy-loading heavy libs
&lt;/h3&gt;

&lt;p&gt;You can't just &lt;code&gt;import 'pdf-lib'&lt;/code&gt; — it's 800KB. So I lazy-load from CDN only when a tool is opened:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;loadPdfLib&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PDFLib&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PDFLib&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;script&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;script&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;script&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://cdn.jsdelivr.net/npm/pdf-lib@1.17.1/dist/pdf-lib.min.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;head&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;script&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;script&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PDFLib&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same pattern for tesseract.js (OCR), jsZIP, jsQR, lunar-javascript. ~12 libs total, all lazy.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. The encoding trap
&lt;/h3&gt;

&lt;p&gt;Halfway through, a friend opened the site on iOS Safari and saw &lt;code&gt;????&lt;/code&gt; characters in some Chinese tool descriptions.&lt;/p&gt;

&lt;p&gt;Cause: A &lt;code&gt;.ts&lt;/code&gt; file got saved in wrong encoding during a copy-paste. &lt;code&gt;????&lt;/code&gt; is valid ASCII, so:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Build passed&lt;/li&gt;
&lt;li&gt;TypeScript compiled&lt;/li&gt;
&lt;li&gt;SHA256 checks passed&lt;/li&gt;
&lt;li&gt;Schema validation passed&lt;/li&gt;
&lt;li&gt;Nothing flagged it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Fix: Added a build-time script that scans all output for &lt;code&gt;????&lt;/code&gt; and fails the build.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson: automated checks can't catch what a human eye sees.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  3. The trailingSlash bug I shipped for 4 months
&lt;/h3&gt;

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

&lt;p&gt;Took 4 months for someone (not me) to notice.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  4. i18n at scale
&lt;/h3&gt;

&lt;p&gt;Some tools (like Taiwan invoice lottery, lunar calendar) make no sense in English. So:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;TOOLS_BY_LOCALE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Locale&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ToolId&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;    &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;globalTools&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;zh-tw&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;globalTools&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;taiwanSpecificTools&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;zh-cn&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;globalTools&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;/en/tools/mars-text/&lt;/code&gt; is a clean 404, not a half-translated tool page.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Per-tool OG images at build time
&lt;/h3&gt;

&lt;p&gt;Started with one shared &lt;code&gt;og-default.svg&lt;/code&gt;. Social shares looked generic.&lt;/p&gt;

&lt;p&gt;Wrote a Node script that generates ~400 per-tool SVGs at build time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// scripts/generate-og-images.ts&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;locale&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;LOCALES&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;toolId&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;TOOLS_BY_LOCALE&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;i18n&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;toolId&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="nf"&gt;writeFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s2"&gt;`public/og/tools/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;toolId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.svg`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nf"&gt;renderOgSvg&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;lang&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;locale&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Runs in 8 seconds. ~400 unique cards. Zero ongoing maintenance.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. llms.txt for AI crawlers
&lt;/h3&gt;

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

&lt;h2&gt;
  
  
  What surprised me
&lt;/h2&gt;

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

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

&lt;p&gt;&lt;strong&gt;&lt;a class="mentioned-user" href="https://dev.to/cantoo"&gt;@cantoo&lt;/a&gt;/pdf-lib saved my PDF encryption tool.&lt;/strong&gt; Original pdf-lib doesn't support encryption. Found out the hard way after shipping a broken tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  What still doesn't work
&lt;/h2&gt;

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

&lt;h2&gt;
  
  
  The honest part
&lt;/h2&gt;

&lt;p&gt;I built 150 tools. I made $0. I have maybe 10 daily users.&lt;/p&gt;

&lt;p&gt;(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.)&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;If you're considering a similar project: &lt;strong&gt;do it as a learning exercise, not as a business.&lt;/strong&gt; If it turns into a business, bonus.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Live: &lt;a href="https://toolboxhub.info" rel="noopener noreferrer"&gt;toolboxhub.info&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;A few that show off the architecture:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;PDF Merge&lt;/strong&gt; — drag-reorder pages, browser-only&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI Token Counter&lt;/strong&gt; — counts tokens for OpenAI / Claude / Gemini&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Image Converter&lt;/strong&gt; — JPG/PNG/WebP/AVIF/BMP/GIF in 6 directions, client-side&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

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

&lt;p&gt;What would you have built differently?&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>showdev</category>
      <category>astro</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
