The problem
Someone finds a pill at a party, or a parent finds a baggie in a kid's room, or a friend overdoses at 2 AM. What do they search? "White round pill M30" or "what is tranq." The results they get are either unreadable government PDFs or 15-year-old forum threads.
I'm building plugreports.com to fix that: a visual, fast, plain-English harm-reduction library covering 111+ street drugs and pharmaceuticals — effects, overdose signs, street prices, legal status — plus drug busts, adulterant alerts, day-by-day quitting guides, and verified 24/7 hotlines for the US, Canada, Europe, Australia and Africa.
Everything is compiled from primary sources (NIDA, DEA, CDC, EMCDDA, WHO) and externally linked, because in this space, citing your sources isn't optional.
The stack (all free tier)
-
Static site generator in Python — content lives in typed Python dicts (
data_drugs.pyis literally the database schema); onebuild.pyrun emits ~160 HTML pages with JSON-LD (MedicalWebPage,FAQPage,NewsArticle), hreflang, sitemap, RSS, and an llms.txt for the AI-search era. -
Cloudflare Pages + Functions — static pages for SEO speed, Pages Functions for the dynamic layer: a PIN-protected
/adminCMS, a KV-backed content API, image upload/storage, and a hydration layer that overlays admin edits onto static pages in real time. New CMS entries even get instant URLs via dynamic routes. -
Workers AI (m2m100) for on-demand page translation, plus a fully static
/es/batch for the SEO-heavy Spanish version. - PWA — service worker + manifest, because someone mid-crisis on bad hotel wifi still needs the hotlines page.
- i18n UI in 10 languages with browser-language auto-detection.
The interesting engineering bits
Hydration over static HTML. Static pages give you Google-love; a CMS gives you instant edits. I do both: every content page loads, then fetches the KV content API and overlays the latest version. Static stays as the SEO fallback, edits go live in seconds without a rebuild.
Functions that defer to static assets. Cloudflare Pages' routing made this tricky — a dynamic route shadows static pages, and Pages serves index.html with a 200 for missing paths, so the function checks a build-time _static.json manifest before rendering anything client-side. Bogus slugs return real 404s.
Media pipeline with zero external services. Admin image uploads go to KV (with R2 auto-upgrade when bound), served through a /media/* function with immutable caching. The admin picker searches the gallery by drug name or street alias, not just filename.
Current numbers
- 111 substance profiles (opioids, RC benzos, cathinones, nitazenes, GLP-1 grey market…) and More to add soon
- 18 profiles added this week, each with FAQPage schema and agency citations
- 32+
real product photos served from KV - 100 Lighthouse, ~200KB of JS total, TTFB ~50ms on the free tier
Why build this?
Harm reduction is one of the few domains where better information literally saves lives that night — a wrong pill doesn't care about your search results. The whole thing is open source on GitHub, and I'd genuinely love contributions: drug data corrections, new profiles (the schema is just a dict), translations.
If you've built something similar — or want to argue about KV vs D1 for this — the comments are open. 🔻
Top comments (0)