<?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: Isiah Swopes</title>
    <description>The latest articles on DEV Community by Isiah Swopes (@isiah_swopes_d8deec2d9ef1).</description>
    <link>https://dev.to/isiah_swopes_d8deec2d9ef1</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%2F4125923%2F21a8d186-00fe-4b86-8b49-c7b501486f12.png</url>
      <title>DEV Community: Isiah Swopes</title>
      <link>https://dev.to/isiah_swopes_d8deec2d9ef1</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/isiah_swopes_d8deec2d9ef1"/>
    <language>en</language>
    <item>
      <title>I updated our drug profile to 563-drug harm-reduction library in 9 languages on Cloudflare's free tier — here's the stack</title>
      <dc:creator>Isiah Swopes</dc:creator>
      <pubDate>Wed, 30 Sep 2026 22:27:56 +0000</pubDate>
      <link>https://dev.to/isiah_swopes_d8deec2d9ef1/i-updated-our-drug-profile-to-563-drug-harm-reduction-library-in-9-languages-on-cloudflares-free-3f4c</link>
      <guid>https://dev.to/isiah_swopes_d8deec2d9ef1/i-updated-our-drug-profile-to-563-drug-harm-reduction-library-in-9-languages-on-cloudflares-free-3f4c</guid>
      <description>&lt;p&gt;Every year, millions of people Google things like "what does fentanyl look like" or "can you mix Xanax and alcohol" — and land on forum speculation. I built &lt;a href="https://plugreports.com" rel="noopener noreferrer"&gt;plugreports&lt;/a&gt; to fix that: a free harm-reduction library with 563 plain-language drug profiles, 61 mixing-danger guides, verified hotlines across nine regions, and an identification video library — in nine languages.&lt;/p&gt;

&lt;p&gt;The interesting part (this &lt;em&gt;is&lt;/em&gt; dev.to): the whole thing runs on Cloudflare's free tier. Here's how.&lt;/p&gt;

&lt;h2&gt;
  
  
  The architecture
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Static generation, but with a CMS.&lt;/strong&gt; A dependency-free Python script (&lt;code&gt;build.py&lt;/code&gt;) reads plain &lt;code&gt;data_*.py&lt;/code&gt; files and emits ~1,100 static HTML pages — every drug profile, guide, comparison and data page — with full JSON-LD (MedicalWebPage, FAQPage, BreadcrumbList), honest &lt;code&gt;lastmod&lt;/code&gt; sitemaps, and real bidirectional hreflang clusters. No framework, no build step beyond &lt;code&gt;python3 build.py&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A KV-backed admin CMS on Pages Functions.&lt;/strong&gt; Static doesn't mean frozen. A PIN-protected &lt;code&gt;/admin&lt;/code&gt; panel (HMAC-token auth against a Pages Function) edits every collection into a Cloudflare KV namespace. Pages Functions serve each route: if the slug exists as a static file it's served directly, and a small &lt;code&gt;hydrate.js&lt;/code&gt; overlays any admin edits from KV onto the static page at runtime. Admin-created items that don't exist statically get rendered client-side. KV always wins over the seed data — reseeding never clobbers an admin edit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Edge SEO injection.&lt;/strong&gt; KV-rendered pages initially shipped a generic shell with &lt;code&gt;&amp;lt;title&amp;gt;plugreports&amp;lt;/title&amp;gt;&lt;/code&gt; — telling Google every dynamic page was a duplicate of the homepage. The fix was a shared &lt;code&gt;_seo.js&lt;/code&gt; helper that strips the shell's head and injects per-item title, description, self-canonical, hreflang and OG tags &lt;em&gt;at the edge&lt;/em&gt;, so crawlers need zero JS execution.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Machine translation via Workers AI.&lt;/strong&gt; Nine languages × 400+ profiles would have taken months by hand. Instead, the profiles are translated in bulk through a tiny &lt;code&gt;/api/translate&lt;/code&gt; endpoint wrapping &lt;code&gt;@cf/meta/m2m100-1.2b&lt;/code&gt; (60 short strings per call, merged into the language packs at build time). Human review happens in the admin panel afterward — machine translation is the draft, not the final word.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Video from KV, with range streaming.&lt;/strong&gt; Identification clips (kept under 3 MB so they load on any connection) are stored as binary KV values and served by a Pages Function that honors HTTP range requests — so HTML5 &lt;code&gt;&amp;lt;video&amp;gt;&lt;/code&gt; scrubbing works with no R2 bucket and no video host. The homepage slider and &lt;code&gt;/watch/&lt;/code&gt; hub hydrate from a &lt;code&gt;videos&lt;/code&gt; collection, so uploading a clip in the admin panel (with a "feature on homepage" checkbox) needs no rebuild.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Never batch concurrent edits to the same source file.&lt;/strong&gt; I corrupted the generator mid-session that way (6× duplicated content, and Python's last-definition-wins masked it). Recovery was &lt;code&gt;raw.githubusercontent.com&lt;/code&gt; + the last known-good commit. Keep your generated site in git and push often.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cache-bust every asset pin.&lt;/strong&gt; Cloudflare's edge caches &lt;code&gt;/assets/*&lt;/code&gt; hard. Every JS/CSS change bumps a &lt;code&gt;?v=&lt;/code&gt; pin and the service-worker cache name together, or you're debugging a week-old stylesheet.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Media in KV is fine — with short cache lifetimes.&lt;/strong&gt; Re-uploads reuse the same filename, so &lt;code&gt;max-age=3600, must-revalidate&lt;/code&gt; instead of immutable year-long caching.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why it matters
&lt;/h2&gt;

&lt;p&gt;The traffic pattern that convinced me this was worth building: search-console data showed huge query volume around identification ("what does X look like"), mixing dangers, and quitting timelines — questions where a wrong answer can kill someone. If the site helps one person recognize an overdose sign at 3 a.m., the whole stack was worth it.&lt;/p&gt;

&lt;p&gt;The project is live at &lt;a href="https://plugreports.com" rel="noopener noreferrer"&gt;plugreports.com&lt;/a&gt; — the &lt;a href="https://plugreports.com/drugs/" rel="noopener noreferrer"&gt;drug library&lt;/a&gt;, &lt;a href="https://plugreports.com/mix/" rel="noopener noreferrer"&gt;mixing dangers&lt;/a&gt;, &lt;a href="https://plugreports.com/hotlines/" rel="noopener noreferrer"&gt;hotline directory&lt;/a&gt; and &lt;a href="https://plugreports.com/watch/" rel="noopener noreferrer"&gt;video library&lt;/a&gt; are the best starting points. Feedback and corrections are welcome via the &lt;a href="https://plugreports.com/suggest/" rel="noopener noreferrer"&gt;suggestion box&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;plugreports is an independent harm-reduction project. It compiles public NIDA, DEA, EMCDDA/EUDA, WHO and CDC data for education and overdose prevention — not medical or legal advice.&lt;/em&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyj644cjk5jj6432t6to0.png" 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/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyj644cjk5jj6432t6to0.png" alt=" " width="799" height="292"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>mentalhealth</category>
      <category>healthydebate</category>
      <category>socialmedia</category>
    </item>
    <item>
      <title>I built a harm-reduction drug-info site on Cloudflare's free tier — 111+ Drug profiles, 150+ pages, $0 hosting</title>
      <dc:creator>Isiah Swopes</dc:creator>
      <pubDate>Tue, 15 Sep 2026 09:06:04 +0000</pubDate>
      <link>https://dev.to/isiah_swopes_d8deec2d9ef1/i-built-a-harm-reduction-drug-info-site-on-cloudflares-free-tier-111-drug-profiles-150-pages-2hkk</link>
      <guid>https://dev.to/isiah_swopes_d8deec2d9ef1/i-built-a-harm-reduction-drug-info-site-on-cloudflares-free-tier-111-drug-profiles-150-pages-2hkk</guid>
      <description>&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;I'm building &lt;a href="https://plugreports.com" rel="noopener noreferrer"&gt;plugreports.com&lt;/a&gt; to fix that: a visual, fast, plain-English harm-reduction library covering &lt;strong&gt;111+ street drugs and pharmaceuticals&lt;/strong&gt; — 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.&lt;/p&gt;

&lt;p&gt;Everything is compiled from primary sources (NIDA, DEA, CDC, EMCDDA, WHO) and externally linked, because in this space, citing your sources isn't optional.&lt;/p&gt;

&lt;h2&gt;
  
  
  The stack (all free tier)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Static site generator in Python&lt;/strong&gt; — content lives in typed Python dicts (&lt;code&gt;data_drugs.py&lt;/code&gt; is literally the database schema); one &lt;code&gt;build.py&lt;/code&gt; run emits ~160 HTML pages with JSON-LD (&lt;code&gt;MedicalWebPage&lt;/code&gt;, &lt;code&gt;FAQPage&lt;/code&gt;, &lt;code&gt;NewsArticle&lt;/code&gt;), hreflang, sitemap, RSS, and an llms.txt for the AI-search era.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cloudflare Pages + Functions&lt;/strong&gt; — static pages for SEO speed, Pages Functions for the dynamic layer: a PIN-protected &lt;code&gt;/admin&lt;/code&gt; CMS, 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.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Workers AI&lt;/strong&gt; (m2m100) for on-demand page translation, plus a fully static &lt;code&gt;/es/&lt;/code&gt; batch for the SEO-heavy Spanish version.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PWA&lt;/strong&gt; — service worker + manifest, because someone mid-crisis on bad hotel wifi still needs the hotlines page.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;i18n UI in 10 languages&lt;/strong&gt; with browser-language auto-detection.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The interesting engineering bits
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Hydration over static HTML.&lt;/strong&gt; 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.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Functions that defer to static assets.&lt;/strong&gt; Cloudflare Pages' routing made this tricky — a dynamic route shadows static pages, and Pages serves &lt;code&gt;index.html&lt;/code&gt; with a 200 for &lt;em&gt;missing&lt;/em&gt; paths, so the function checks a build-time &lt;code&gt;_static.json&lt;/code&gt; manifest before rendering anything client-side. Bogus slugs return real 404s.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Media pipeline with zero external services.&lt;/strong&gt; Admin image uploads go to KV (with R2 auto-upgrade when bound), served through a &lt;code&gt;/media/*&lt;/code&gt; function with immutable caching. The admin picker searches the gallery by drug name &lt;em&gt;or&lt;/em&gt; street alias, not just filename.&lt;/p&gt;

&lt;h2&gt;
  
  
  Current numbers
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;111 substance profiles (opioids, RC benzos, cathinones, nitazenes, GLP-1 grey market…) and More to add soon&lt;/li&gt;
&lt;li&gt;18 profiles added this week, each with FAQPage schema and agency citations&lt;/li&gt;
&lt;li&gt;32+
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fohapl39lhy5qwe8i1n2n.png" alt=" " width="800" height="361"&gt; real product photos served from KV&lt;/li&gt;
&lt;li&gt;100 Lighthouse, ~200KB of JS total, TTFB ~50ms on the free tier&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why build this?
&lt;/h2&gt;

&lt;p&gt;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 &lt;a href="https://github.com/coke-blog/plugreports" rel="noopener noreferrer"&gt;open source on GitHub&lt;/a&gt;, and I'd genuinely love contributions: drug data corrections, new profiles (the schema is just a dict), translations.&lt;/p&gt;

&lt;p&gt;If you've built something similar — or want to argue about KV vs D1 for this — the comments are open. 🔻&lt;/p&gt;

</description>
      <category>health</category>
      <category>python</category>
      <category>cloudflarechallenge</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
