Build Log

Building in public

This is the working journal for everything I'm building — PainBeacon, BestRaces, Rich Shea Butter, this site, and whatever comes next. Real milestones, real numbers, and the things that broke along the way. Most of it is built with AI agents (Claude Code, mostly) with me directing; the mistakes are a team effort.

New entries roughly twice a month. Newest first.

BestRaces: the race map, the crawl budget, and a newsletter engine

Two weeks of heavy shipping on BestRaces. The headline feature is the race map: 4,600 races plotted nationwide, with actual course routes traced on the map — seeded from OpenStreetMap data, with race directors able to upload their own GPX files. The course line even animates itself from start to finish. Then the infrastructure caught up: a newsletter engine with a twice-monthly digest, race-week automation, and branded social graphics generated for every article.

BestRaces homepage — Every ranking, with the receipts
The promise on the tin: 4,863 U.S. races scored, methodology public, rankings never sold.
The Rundown — BestRaces news and analysis section
The Rundown: data-desk analysis articles that feed the newsletter digest.

Shipped

  • Interactive race map with OSM-seeded course traces + director GPX upload — and an animated course line
  • Newsletter engine: twice-monthly digest, race-week automation, unsubscribe infrastructure, badge-stamped category photos
  • Email capture across the site: plan-by-email, race-page signup, calendar moments, race segmentation
  • Every dated race got a downloadable .ics calendar file; every race page got a lodging panel
  • Fixed the four indexing faults that were starving the crawl budget — then documented the SEO invariants so they stay fixed

What broke

  • An email API key rotation took five commits to debug — we ended up building a temporary workflow just to prove the alert path worked, then tore it out. The fix: surface the provider's actual error instead of guessing.
  • Races at the same coordinates were hiding each other's map pins. Now they cluster.
  • Non-US races were quietly leaking into the US rankings. Barred at the filter.
  • A race with a 250-character name broke the whole build. Slugs are now capped at 200.
Takeaway: when a deploy can fail silently, the first feature you owe yourself is the alarm. We now email on any failed Cloudflare deploy — the debugging saga was worth it.

PainBeacon publishes original research: Pain Care Deserts

PainBeacon graduated from directory to publisher. The Pain Care Deserts study analyzed all 12,487 clinic locations in the federal NPI registry against every U.S. county and found that 1,906 of 3,144 counties — 61% — have no pain management clinic at all. About 38 million Americans live in one of these deserts. The study shipped with a county-level choropleth map, and the finding is exactly the kind of original data that earns citations from journalists and AI answer engines alike.

Pain Care Deserts study with county choropleth map
1,906 counties, mapped: the darker the county, the farther its residents travel for pain care.

Shipped

  • Pain Care Deserts research page with county dataset and choropleth
  • Clinic map pins now use the logo and group co-located clinics
  • Google Places backfills for hours and websites, engineered to stay inside the API free tier
  • A stock-photo registry so no two pages can ever share the same image
  • Monthly outreach report that detects real listing changes, not just first-time data fills

What broke

  • The first cut of the study counted all of Connecticut as a pain care desert. Connecticut replaced its counties with planning regions in 2022 — our county join didn't know that. Fixed, re-verified.
  • Google confirmed our Places calls were billing under an Enterprise SKU — backfills got capped at 450/run and rebalanced the same day.
  • A snapshot comparison silently matched nothing because one side stored NPI as a number and the other as text. Coerce, then compare.
Takeaway: original research is the best link-earning and AI-citation asset a directory can build — but validate your geography joins. Somewhere, a state has always changed its counties.

The AEO sprint: teaching search and answer engines who we are

Four days spent making every site legible to Google, Bing, and the AI answer engines (ChatGPT Search, Perplexity, Claude). The centerpiece is a JSON-LD entity graph on richshea.com that formally connects me to every venture — so when an AI is asked about PainBeacon or BestRaces, the founder relationship is machine-readable fact, not inference. All sites got verified in Google Search Console as domain properties, imported into Bing Webmaster Tools in one OAuth step, and had their robots.txt policies deliberately set: AI crawlers welcomed everywhere we want citations.

{
  "@type": "Person",
  "name": "Richard Shea",
  "founder": [
    { "@id": "https://painbeacon.com/#org" },
    { "@id": "https://bestraces.com/#org" },
    { "@id": "https://richsheabutter.com/#org" }
  ],
  "worksFor": { "@id": "https://candorhealthcare.com/#org" }
}

Shipped

  • JSON-LD @graph entity graph: Person + WebSite + one Organization node per venture, cross-linked
  • Google Search Console domain properties + sitemaps for richshea.com and bestraces.com
  • Bing Webmaster Tools: all seven GSC properties imported in one step (Bing powers ChatGPT Search and Copilot)
  • Reciprocal dofollow footer links between the venture sites and richshea.com — a deliberate entity signal
  • AI-crawler policy set site by site: GPTBot, ClaudeBot, PerplexityBot all explicitly allowed where we want to be cited

What broke

  • BestRaces had no sitemap at all — /sitemap.xml was serving the homepage HTML. Adding the Astro sitemap integration produced a proper index with 5,014 URLs.
  • PainBeacon's IndexNow submission job kept failing in CI: Cloudflare's Bot Fight Mode was challenging our own GitHub runner. Fix: fetch via the raw pages.dev origin, submit the canonical URLs.
  • Cloudflare's managed robots.txt was silently blocking AI crawlers on bestraces.com — the opposite of the strategy. Two dashboard toggles later, policy and reality agree.
Takeaway: check what your CDN is doing in your name. Two of our three robots.txt problems were configuration we didn't know we had.

Rich Shea Butter: a fact-first directory in a weekend — then the hard part

The fastest launch yet: richsheabutter.com went from empty repo to live site in a day — a custom static-site generator (one build.js, no framework), then a full redesign two days later into a clean, clinical comparison system. Every shea butter sold in the US, compared on origin, refinement, certifications, and verified price per ounce. No sponsored rankings. The "Made with Shea" section followed: finished products — soaps, balms, masques — organized by category with honest picks.

Rich Shea Butter homepage — Every shea butter, compared on facts
15 products tracked, 5 certified organic, verified $0.67–$2.67 per ounce — no adjectives.

Shipped

  • Custom static generator with an image pipeline and per-product data heroes
  • Full "clean-clinical" redesign with self-hosted type, price-per-ounce chart with every bar labeled
  • "Made with Shea" finished-products section with category jump-menu
  • 301 redirects for every retired product URL, plus a de-dupe guard so near-identical entries can't slip in
  • Amazon affiliate integration and Cloudflare Email Routing on the domain

What broke

  • Stale CSS kept serving after deploys — fixed with edge revalidation on HTML.
  • Products kept disappearing from Amazon. Two delisted in July, one more in August, six out-of-stock items purged and replaced. A directory is a subscription to maintenance, not a launch.
Takeaway: the site took a weekend; keeping 15 product listings honest is the real product. Scheduled ASIN spot-checks are now part of the routine.

Losing the old site, rebuilding richshea.com in ten days

This site used to live on Adobe Portfolio — until the Adobe account that hosted it was gone, and the site with it. The rebuild became the excuse to do it right: a single self-contained HTML file (no framework, no build step), deployed as a Cloudflare Worker connected straight to GitHub. Push to main, live in about a minute. The DNS zone moved from Hostway to Cloudflare along the way — carefully, since the richshea.com email had to keep flowing through Hostway's mail servers the whole time.

richshea.com homepage hero
The rebuilt richshea.com — one HTML file, deployed by git push.

Shipped

  • Single-file static site: hero, three pillars, Candor Healthcare feature, nine project cards, contact form
  • Cloudflare Worker hosting, git-connected — push to main auto-deploys
  • DNS zone migrated to Cloudflare with Hostway email preserved (unproxied mail records)
  • Contact form via Web3Forms — submissions land straight in Gmail, no backend

What broke

  • Hostway's DNS panel takes up to 4 hours to publish changes — which looks exactly like you did it wrong. Patience is a debugging tool.
  • Mid-migration we discovered the domain's auto-renew was off. The kind of thing you only find when you're actually looking.
Takeaway: losing the hosted platform was the best thing that happened to this site. Owning the file, the repo, and the DNS means no platform can take it away again.