I've been building my own websites for 25 years — more than a dozen of them, from
event sites to whatever the moment needed. These are the first I've built with AI
agents (Claude Code, mostly) with me directing, and the difference isn't how fast
they ship — it's what they can do: real data, real pipelines, real research.
This journal tracks all of it — PainBeacon, BestRaces, Rich Shea Butter, this site,
and whatever comes next. Real milestones, real numbers, and the things that broke
along the way. The mistakes are a team effort.
PainBeacon starts charging — and builds the thing worth paying for
Seven weeks since the last entry. The cadence slipped; the shipping didn't.
PainBeacon spent them crossing
the line every directory eventually has to cross: it stopped being only a free index of every pain
practice in the country and started asking some of them for money.
Four live payment links now sit on the practices page — Enhanced at $50/month, Featured at
$500/month, each with a launch offer of four months for the price of three. They were created by a
script instead of four passes through the Stripe dashboard, and the amounts are read from the site's
own config, so a payment link cannot charge a price the page doesn't advertise. Every link asks for
practice name and NPI, which is what makes a payment identifiable. Fulfillment stays deliberately
manual: money lands in Stripe and nothing changes until a human sets the tier.
Two paid tiers, four payment links, and a launch offer priced from the same config the page reads.Metro pages ignore state lines: 449 clinics within 40 miles of downtown DC, across three jurisdictions.
Shipped
Paid listings turned on: two tiers, four Stripe payment links, prepaid terms, and a link checker that fails the build if a button points at a retired or duplicated link
The Practice Kit — five front-desk forms (listing audit, Google Business Profile checklist, review protocol, new-patient phone script, insurance sheet), free but gated behind a claimed-and-verified listing. The kit is the reason to claim; the claim is what makes the listing worth selling.
Metro landing pages that cross state lines — one Washington, DC page holding DC, Northern Virginia and the Maryland suburbs, assigned from coordinates at build time so they can't drift from the zone names
The Pain Care Deserts study became infrastructure: a JSON endpoint and an embeddable choropleth, because an embed carries a link back. Publishable only because every input is federal public domain — the map's own clinic points are not, and the scripts now say so where someone extending them would look.
Outreach aimed at the clinics AI assistants already cite us for: Bing's AI Performance report lists the queries where Copilot used PainBeacon as a source, and a matcher sorts them into branded (a practice to contact) and category (nobody to contact) by word rarity rather than a keyword list — 4 branded out of 17 on the October 1 report, no false positives
Weekly articles kept shipping throughout: epidural steroid injections, sciatica, non-opioid pain management, spinal cord stimulators, and what to ask before agreeing to any procedure
What broke
Articles were dated ahead of their publication day, so the social automation never saw them as new. Now they're dated the day they merge.
The WebSub ping that tells search engines a new article exists was reporting a rejection as a success — it had been failing quietly and claiming otherwise.
The mobile home hero photo covered the entire hero and buried the lead card. Three passes to get the band short enough.
The outreach pipeline wouldn't run at all: it couldn't find the Supabase key under the name it had been given, and couldn't read a .env file written by Notepad. Both now handled.
Takeaway: the warmest outreach list we had was already sitting in Bing Webmaster Tools — the practices an AI assistant had cited us for by name. A citation isn't a backlink, but it tells you exactly who already treats you as a source, and the pitch is checkable by the recipient in ten seconds.
BestRaces: deleting races to make the rankings true
Most of this stretch on BestRaces
made the index smaller. We deleted 99 listings that were never races — tournaments,
meetings, camps, volunteer sign-ups, an art workshop — and kept them out with a denylist that
records a reason for each one. That scan only became possible after finding out why the catalog
looked the way it did: RunSignup's race search is sorted by name and stops at 5,000 results, so every
sync we had ever run only saw names from the start of the alphabet to about "Fantasy". Searching
state by state instead found 16,101 upcoming U.S. races.
Then the certifications. A course certified by USA Track & Field is a quarter of a race's score,
and the field had been measured on 41 races out of 5,456 — both syncs created every race with the
value empty and nothing ever filled it. Matching all 15,451 active USATF road courses against the
eligible races produced 357 certifications, plus 365 near-misses that no rule could decide. Those
were worked by hand: 50 accepted, 26 held for a policy call, 290 rejected, with 27 of them checked
against the actual USATF certificate PDFs. Races carrying a composite score went from 427 to 632,
and no flagship ranking moved.
Search grew filters — distance, surface and when — all kept in the URL, so a filtered view is a shareable link.Race-day weather on 6,081 race pages, from public-domain NOAA records — naming the station and its distance.
Shipped
USATF certification audit and review: 357 written by rule, 365 near-misses decided one at a time, every decision recorded with its reason
A triathlon sanctioning audit, and the article that came out of it — USA Triathlon issues sanction numbers but publishes no list we're allowed to read, so we read all 98 race listings instead and found 16 that will say. The piece owns our own error first: two duathlons had ranked 12th and 14th in New York because a running formula scored a bike leg like a mile.
Race-day weather on 6,081 race pages — five years of highs, lows and rainfall at the nearest NOAA station, from GHCN-Daily because the convenient free weather APIs forbid commercial use
Runner ratings live on the 3,824 race pages with evidence of a past edition, with averages withheld until five confirmed ratings and every rejection path tested against production before launch
Search filters by distance, surface and date; lodging measured for 1,533 races; the homepage's metro and state grid collapsed into a compact near-you strip
1,746 dead race URLs now redirect to their state ranking page instead of 404ing — found because Google referred a runner to a race we deleted three days after it was run. Junk we deleted on purpose keeps its 404.
Race-entry giveaways with official rules and a verifiable draw, a race-director share link, and the first organizer reshare: Asbury Park put our New Jersey ranking in their stories, unprompted
What broke
Two days of deploy-failure alerts that weren't our fault at all — Cloudflare's initialize stage failing after 28 minutes on an already-live commit, before it ever reached our code. The alarm we built in August worked; the first thing it caught was the host.
The first Instagram Reel's numbers were partly our own: the shares and most of the likes turned out to be self-engagement. Corrected in the log the next day. The honest read — 404 viewers, 69% of them non-followers, zero profile visits, zero follows — was more useful than the flattering one.
99 non-races, then 7 more, then an art workshop and a bootcamp class: the junk kept surfacing in ones and twos until the filter moved up to ingest.
Takeaway: deleting 99 listings, rejecting 290 certification near-misses and redirecting 1,746 dead URLs doesn't look like progress on any dashboard. It is the only kind that matters for a site whose promise is every ranking, with the receipts.
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.
The promise on the tin: 4,863 U.S. races scored, methodology public, rankings never sold.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
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.
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.
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.
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.
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.