<?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: Bob Kim</title>
    <description>The latest articles on DEV Community by Bob Kim (@bob_kim_marley).</description>
    <link>https://dev.to/bob_kim_marley</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4149512%2F9cc9ea04-048f-495f-9e94-a15f88fb8ef0.png</url>
      <title>DEV Community: Bob Kim</title>
      <link>https://dev.to/bob_kim_marley</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/bob_kim_marley"/>
    <language>en</language>
    <item>
      <title>I ship 118 dev tools as a single static export: here's the setup</title>
      <dc:creator>Bob Kim</dc:creator>
      <pubDate>Tue, 29 Sep 2026 11:31:10 +0000</pubDate>
      <link>https://dev.to/bob_kim_marley/i-ship-118-dev-tools-as-a-single-static-export-heres-the-setup-a2i</link>
      <guid>https://dev.to/bob_kim_marley/i-ship-118-dev-tools-as-a-single-static-export-heres-the-setup-a2i</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Foqtp1ac8pep4q3vro9oh.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Foqtp1ac8pep4q3vro9oh.gif" alt=" " width="800" height="582"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I run &lt;a href="https://devpick.sh" rel="noopener noreferrer"&gt;devpick.sh&lt;/a&gt;, a collection of 118 small developer tools (JSON formatter, cron explainer, subnet calculator, UTM builder, that kind of thing). The whole site is one Next.js app that builds to static HTML and deploys to Cloudflare Pages. No backend, no database, hosting costs about $0.&lt;/p&gt;

&lt;p&gt;I just open-sourced it under MIT (&lt;a href="https://github.com/kimbobmarley03/devpick.sh" rel="noopener noreferrer"&gt;repo&lt;/a&gt;). The interesting part isn't the tools, it's the machinery for running 118 near-identical pages without drowning.&lt;/p&gt;

&lt;h3&gt;
  
  
  One folder per tool
&lt;/h3&gt;

&lt;p&gt;Every tool is a self-contained route: &lt;code&gt;app/&amp;lt;tool-name&amp;gt;/page.tsx&lt;/code&gt; plus an optional &lt;code&gt;-tool.tsx&lt;/code&gt; client component. That's the whole convention. The page exports its own &lt;code&gt;metadata&lt;/code&gt; (title, description, canonical, OpenGraph) and a JSON-LD block; the tool component is pure client-side logic. Adding tool #119 means copying a folder. There are no shared "tool frameworks". I tried abstracting early and every abstraction leaked, because tools genuinely differ. Convention over framework.&lt;/p&gt;

&lt;h3&gt;
  
  
  The SEO audit that fails the build
&lt;/h3&gt;

&lt;p&gt;With 118 pages, metadata rot is the default state of the universe. Someone (me) writes a 190-character description, forgets a canonical, duplicates a title, and nobody notices for months.&lt;/p&gt;

&lt;p&gt;So &lt;code&gt;npm run build&lt;/code&gt; is actually &lt;code&gt;next build &amp;amp;&amp;amp; next-sitemap &amp;amp;&amp;amp; npm run audit:seo&lt;/code&gt;. The audit script (&lt;code&gt;scripts/audit-seo.mjs&lt;/code&gt;) checks every route for title present and unique, description present and under ~160 chars, canonical set, breadcrumb JSON-LD valid. It exits non-zero on any failure, which fails the Cloudflare deploy. The one time it bit me: I lengthened the UTM builder description during a revamp and the build went red. That's the system working.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sitemap and structured data
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;next-sitemap&lt;/code&gt; generates the sitemap from the route tree at build time (118 URLs, no manual list to maintain). Each tool page emits &lt;code&gt;WebApplication&lt;/code&gt; JSON-LD (name, URL, free offer) via the shared &lt;code&gt;ToolLayout&lt;/code&gt;, which also emits the &lt;code&gt;BreadcrumbList&lt;/code&gt;. Structured data lives in two components, not 118 pages.&lt;/p&gt;

&lt;h3&gt;
  
  
  Privacy-safe analytics
&lt;/h3&gt;

&lt;p&gt;I wanted to know which tools people actually complete vs. abandon, but the entire premise is "paste secrets here safely", so the analytics had to be provably blind. &lt;code&gt;lib/analytics.ts&lt;/code&gt; sends only page views and tool outcomes (&lt;code&gt;completed&lt;/code&gt;, &lt;code&gt;errored&lt;/code&gt;) with string/number/boolean params. Tool inputs, filenames, and generated output never enter the event payload. That's enforced by how the code is structured, not by a policy doc. It's Google Analytics underneath (ironic, I know), but the payload discipline is what matters. If you're building privacy-sensitive tools: keep the event schema separate from the tool state, and never let the two meet.&lt;/p&gt;

&lt;h3&gt;
  
  
  What I'd do differently
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Not Next.js.&lt;/strong&gt; It's a static site. I used Next for colocated per-route metadata and &lt;code&gt;output: export&lt;/code&gt;. An SSG like Astro would've been lighter. I picked the tool I knew to ship faster. Right call for a side project; wrong call if the build ever needs to be fast. (It takes a few minutes for 118 pages.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shareable state earlier.&lt;/strong&gt; I only recently made tool state sync to the URL query string (UTM builder first). Every tool should have had this from day one. It's the cheapest linkability feature that exists and I left it on the table for months.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/UPLOAD%2520utm-share.gif%2520VIA%2520THE%2520DEV.TO%2520EDITOR%2520HERE" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/UPLOAD%2520utm-share.gif%2520VIA%2520THE%2520DEV.TO%2520EDITOR%2520HERE" alt="UTM builder share-link flow" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The MCP server might be a gimmick.&lt;/strong&gt; I wrapped 43 tools as MCP tools for AI agents. It was cheap to build. I have no evidence anyone wants it. I'll delete it if it rots.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The takeaway
&lt;/h3&gt;

&lt;p&gt;If you're sitting on a pile of similar pages (docs, tools, templates), the pattern is: convention-based routes, a build-time audit that fails the deploy, a generated sitemap, and structured data in shared layout components. The tools are commodity. The discipline is the product.&lt;/p&gt;

&lt;p&gt;Repo's MIT if you want to steal the audit script or the whole thing: &lt;a href="https://github.com/kimbobmarley03/devpick.sh" rel="noopener noreferrer"&gt;kimbobmarley03/devpick.sh&lt;/a&gt;. PRs welcome, especially for tools that are wrong. With 118 of them, some certainly are.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>nextjs</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
