I run devpick.sh, 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.
I just open-sourced it under MIT (repo). The interesting part isn't the tools, it's the machinery for running 118 near-identical pages without drowning.
One folder per tool
Every tool is a self-contained route: app/<tool-name>/page.tsx plus an optional -tool.tsx client component. That's the whole convention. The page exports its own metadata (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.
The SEO audit that fails the build
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.
So npm run build is actually next build && next-sitemap && npm run audit:seo. The audit script (scripts/audit-seo.mjs) 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.
Sitemap and structured data
next-sitemap generates the sitemap from the route tree at build time (118 URLs, no manual list to maintain). Each tool page emits WebApplication JSON-LD (name, URL, free offer) via the shared ToolLayout, which also emits the BreadcrumbList. Structured data lives in two components, not 118 pages.
Privacy-safe analytics
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. lib/analytics.ts sends only page views and tool outcomes (completed, errored) 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.
What I'd do differently
-
Not Next.js. It's a static site. I used Next for colocated per-route metadata and
output: export. 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.) - Shareable state earlier. 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.
- The MCP server might be a gimmick. 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.
The takeaway
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.
Repo's MIT if you want to steal the audit script or the whole thing: kimbobmarley03/devpick.sh. PRs welcome, especially for tools that are wrong. With 118 of them, some certainly are.


Top comments (0)