I updated our drug profile to 563-drug harm-reduction library in 9 languages on Cloudflare's free tier - here's the stack
Here's a thinking process:
-
Analyze User Request:
- Role: Expert editor for DevPlace (social network for software developers)
- Task: Reformat raw article body into clean, readable Markdown for a technical audience
- Rules:
- Break into short, well-structured paragraphs separated by blank lines
- Add Markdown section headings (## Heading) where topic clearly shifts
- Use bullet/numbered lists for enumerations, inline code/fenced code blocks for code/commands/identifiers
- Preserve every fact, name, number, quotation exactly as given - never invent, add, remove, or reorder info
- Only restructure and lightly polish wording for flow/grammar; no intro, conclusion, opinion, or own commentary
- Do not repeat article title as heading
- Do not wrap whole answer in code fence
- Output only reformatted article body as Markdown, nothing else
-
Analyze the Input Text:
- Title: "I updated our drug profile to 563-drug harm-reduction library in 9 languages on Cloudflare's free tier - here's the stack"
- Article body: A single long paragraph (with some internal structure) that needs reformatting.
- I need to carefully read and preserve all facts, numbers, names, quotations exactly as given.
- I need to identify topic shifts and add
##headings. - I need to use code blocks/inline code where appropriate.
- I need to break into short paragraphs.
Let me extract the content and structure it:
The article seems to have these natural sections/topics:
- Introduction/overview of plugreports and its purpose
- Architecture: Static generation with CMS
- KV-backed admin CMS on Pages Functions
- Edge SEO injection
- Machine translation via Workers AI
- Video from KV with range streaming
- What I'd do differently (lessons learned)
- Why it matters / conclusion
But I must be careful: the rules say "Add Markdown section headings (## Heading) where the topic clearly shifts, so the article scans well." and "Only restructure and lightly polish wording for flow and grammar; do not add an introduction, conclusion, opinion, or commentary of your own." Also "Do not repeat the article title as a heading".
I need to preserve every fact, name, number, quotation exactly. So I can't change numbers, names, etc.
Let me outline the sections based on the text flow:
Section 1: Overview / Purpose
- 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 plugreports 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.
- The interesting part (this is dev.to): the whole thing runs on Cloudflare's free tier.
- Here's how.
Section 2: Architecture
- Static generation, but with a CMS.
- A dependency-free Python script (build.py) reads plain data_*.py files and emits ~1,100 static HTML pages - every drug profile, guide, comparison and data page - with full JSON-LD (MedicalWebPage, FAQPage, BreadcrumbList), honest lastmod sitemaps, and real bidirectional hreflang clusters.
- No framework, no build step beyond python3 build.py.
Section 3: KV-backed admin CMS on Pages Functions
- Static doesn't mean frozen.
- A PIN-protected /admin 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 hydrate.js 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.
Section 4: Edge SEO injection
- Edge SEO injection. KV-rendered pages initially shipped a generic shell with plugreports - telling Google every dynamic page was a duplicate of the homepage.
- The fix was a shared _seo.js helper that strips the shell's head and injects per-item title, description, self-canonical, hreflang and OG tags at the edge, so crawlers need zero JS execution.
Section 5: Machine translation via Workers AI
- Machine translation via Workers AI.
- Nine languages × 400+ profiles would have taken months by hand.
- Instead, the profiles are translated in bulk through a tiny /api/translate endpoint wrapping @cf/meta/m2m100-1.2b (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.
Section 6: Video from KV, with range streaming
- Video from KV, with range streaming.
- 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 <video> scrubbing works with no R2 bucket and no video host.
- The homepage slider and /watch/ hub hydrate from a videos collection, so uploading a clip in the admin panel (with a "feature on homepage" checkbox) needs no rebuild.
Section 7: What I'd do differently
- What I'd do differently
- Never batch concurrent edits to the same source file. I corrupted the generator mid-session that way (6× duplicated content, and Python's last-definition-wins masked it). Recovery was raw.githubusercontent.com + the last known-good commit.
- Keep your generated site in git and push often.
- Cache-bust every asset pin. Cloudflare's edge caches /assets/* hard. Every JS/CSS change bumps a?v= pin and the service-worker cache name together, or you're debugging a week-old stylesheet.
- Media in KV is fine - with short cache lifetimes. Re-uploads reuse the same filename, so max-age=3600, must-revalidate instead of immutable year-long caching.
Section 8: Why it matters
- Why it matters
- 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.
- The project is live at plugreports.com - the drug library, mixing dangers, hotline directory and video library are the best starting points.
- Feedback and corrections are welcome via the suggestion box.
- 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.
Wait, I need to check the exact text for quotations and preserve them. The user said "Preserve every fact, name, number, and quotation exactly as given." I see some quoted strings like "what does X look like", "can you mix Xanax and alcohol", etc. I need to keep them exactly as is, including the quotes.
Also, check for any code blocks or inline code. The text has: build.py, data_*.py, python3 build.py, HMAC-token, /admin panel, KV, Pages Functions, hydrate.js, _seo.js, @cf/meta/m2m100-1.2b, ?v= pin, service-worker, etc. I should use inline code for identifiers/commands, and fenced code blocks only if there's a code block in the original. The original doesn't have fenced code blocks, but has code-like text. I'll use inline code for those.
Let me re-read the original carefully and extract exactly.
Original text (I'll copy it here for reference):
"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 plugreports 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. The interesting part (this is dev.to): the whole thing runs on Cloudflare's free tier. Here's how. The architecture Static generation, but with a CMS. A dependency-free Python script ( build.py ) reads plain data_.py files and emits ~1,100 static HTML pages - every drug profile, guide, comparison and data page - with full JSON-LD (MedicalWebPage, FAQPage, BreadcrumbList), honest lastmod sitemaps, and real bidirectional hreflang clusters. No framework, no build step beyond python3 build.py . A KV-backed admin CMS on Pages Functions. Static doesn't mean frozen. A PIN-protected /admin 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 hydrate.js 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. Edge SEO injection. KV-rendered pages initially shipped a generic shell with plugreports - telling Google every dynamic page was a duplicate of the homepage. The fix was a shared _seo.js helper that strips the shell's head and injects per-item title, description, self-canonical, hreflang and OG tags at the edge , so crawlers need zero JS execution. Machine translation via Workers AI. Nine languages × 400+ profiles would have taken months by hand. Instead, the profiles are translated in bulk through a tiny /api/translate endpoint wrapping @cf/meta/m2m100-1.2b (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. Video from KV, with range streaming. 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 <video> scrubbing works with no R2 bucket and no video host. The homepage slider and /watch/ hub hydrate from a videos collection, so uploading a clip in the admin panel (with a "feature on homepage" checkbox) needs no rebuild. What I'd do differently - Never batch concurrent edits to the same source file. I corrupted the generator mid-session that way (6× duplicated content, and Python's last-definition-wins masked it). Recovery was raw.githubusercontent.com + the last known-good commit. Keep your generated site in git and push often. - Cache-bust every asset pin. Cloudflare's edge caches /assets/ hard. Every JS/CSS change bumps a?v= pin and the service-worker cache name together, or you're debugging a week-old stylesheet. - Media in KV is fine - with short cache lifetimes. Re-uploads reuse the same filename, so max-age=3600, must-revalidate instead of immutable year-long caching. Why it matters 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. The project is live at plugreports.com - the drug library , mixing dangers, hotline directory and video library are the best starting points. Feedback and corrections are welcome via the suggestion box . 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. Top comments (0)"
Important: The original has some formatting quirks. I need to preserve exact text. Note the quotes: "what does fentanyl look like" and "can you mix Xanax and alcohol" - these have curly quotes in the original? Actually in the text it's written with regular quotes but with spaces: "what does fentanyl look like". I'll keep them as they appear.
Also note: "plugreports" appears without quotes sometimes. I'll keep as is.
Now, I need to structure into Markdown with ## headings where topic shifts. I need to identify shifts:
- First paragraph: Overview, purpose, the problem, plugreports description, Cloudflare free tier mention.
- Architecture section: Static generation with CMS, build.py, data files, JSON-LD, etc.
- KV-backed admin CMS: PIN-protected /admin, Pages Functions, hydrate.js, KV wins over seed.
- Edge SEO injection: generic shell, _seo.js helper, crawlers need zero JS.
- Machine translation via Workers AI: bulk translation, @cf/meta/m2m100-1.2b, human review.
- Video from KV, with range streaming: binary KV, HTTP range requests, <video> scrubbing, no R2.
- What I'd do differently: lessons learned, concurrent edits, git, cache-busting, cache lifetimes.
- Why it matters: traffic patterns, search console data, purpose, live project, feedback, data sources.
I need to make sure each section is a heading, and paragraphs are short, separated by blank lines. I'll use ## for each heading. I must not repeat the article title as a heading, so I won't add a heading for the title.
Let me draft the Markdown:
Overview and Purpose
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 plugreports 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. The interesting part (this is dev.to): the whole thing runs on Cloudflare's free tier. Here's how.
Architecture: Static Generation with a CMS
A dependency-free Python script (build.py) reads plain data_*.py files and emits ~1,100 static HTML pages - every drug profile, guide, comparison and data page - with full JSON-LD (MedicalWebPage, FAQPage, BreadcrumbList), honest lastmod sitemaps, and real bidirectional hreflang clusters. No framework, no build step beyond python3 build.py.
KV-Backed Admin CMS on Pages Functions
Static doesn't mean frozen. A PIN-protected /admin 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 hydrate.js 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.
Edge SEO Injection
KV-rendered pages initially shipped a generic shell with plugreports - telling Google every dynamic page was a duplicate of the homepage. The fix was a shared _seo.js helper that strips the shell's head and injects per-item title, description, self-canonical, hreflang and OG tags at the edge, so crawlers need zero JS execution.
Machine Translation via Workers AI
Nine languages × 400+ profiles would have taken months by hand. Instead, the profiles are translated in bulk through a tiny /api/translate endpoint wrapping @cf/meta/m2m100-1.2b (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.
Video from KV, with Range Streaming
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 <video> scrubbing works with no R2 bucket and no video host. The homepage slider and /watch/ hub hydrate from a videos collection, so uploading a clip in the admin panel (with a "feature on homepage" checkbox) needs no rebuild.
What I'd Do Differently
- Never batch concurrent edits to the same source file. I corrupted the generator mid-session that way (6× duplicated content, and Python's last-definition-wins masked it). Recovery was raw.githubusercontent.com + the last known-good commit.
- Keep your generated site in git and push often.
- Cache-bust every asset pin. Cloudflare's edge caches /assets/* hard. Every JS/CSS change bumps a?v= pin and the service-worker cache name together, or you're debugging a week-old stylesheet.
- Media in KV is fine - with short cache lifetimes. Re-uploads reuse the same filename, so max-age=3600, must-revalidate instead of immutable year-long caching.
Why It Matters
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. The project is live at plugreports.com - the drug library, mixing dangers, hotline directory and video library are the best starting points. Feedback and corrections are welcome via the suggestion box. 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.
Wait, I need to check if I'm preserving exact quotes and formatting. Let's verify some critical pieces:
-
"what does fentanyl look like" - in original: "what does fentanyl look like" - I have it in the first paragraph and also in "Why It Matters" section. I need to make sure it's exactly the same. In the original, it appears twice: once in the very first sentence: "Every year, millions of people Google things like "what does fentanyl look like" or "can you mix Xanax and alcohol" ..." and later in "Why it matters": "search-console data showed huge query volume around identification ("what does X look like"), mixing dangers, and quitting timelines". Note the difference: first is "what does fentanyl look like", later is "what does X look like". I must preserve both exactly as given. In my draft, I have "what does fentanyl look like" in the first paragraph, and "what does X look like" in the Why It Matters section. That's correct.
-
"can you mix Xanax and alcohol" - I have it in the first paragraph.
-
"plugreports" - appears many times. I'll keep as is.
-
"build.py" - inline code.
-
"data_*.py" - inline code.
-
"python3 build.py" - inline code.
-
"HMAC-token" - I have it as (HMAC-token auth against a Pages Function). Original: "A PIN-protected /admin panel (HMAC-token auth against a Pages Function)". I'll keep exact phrasing.
-
"@cf/meta/m2m100-1.2b" - inline code.
-
"?v= pin" - I have "a?v= pin". Original: "Every JS/CSS change bumps a?v= pin and the service-worker cache name together". I'll keep exact.
-
"max-age=3600, must-revalidate" - exact.
-
"6× duplicated content" - exact.
-
"Python's last-definition-wins" - exact.
-
"raw.githubusercontent.com" - exact.
-
"last known-good commit" - exact.
-
"NIDA, DEA, EMCDDA/EUDA, WHO and CDC" - exact.
-
"not medical or legal advice" - exact.
-
"Top comments (0)" - this seems like a comment section footer, but the user said "Output only the reformatted article body as Markdown, nothing else." I should exclude "Top comments (0)" as it's likely not part of the article body, or maybe it is? The original ends with "Top comments (0)". The user said "Preserve every fact, name, number, and quotation exactly as given." But "Top comments (0)" is not a fact/name/number/quotation in the article content; it's probably a platform artifact. The user also said "Do not add an introduction, conclusion, opinion, or commentary of your own." and "Output only the reformatted article body as Markdown, nothing else." I think I should exclude "Top comments (0)" since it's not part of the article's content, but if it's part of the raw body, maybe I should include it? Looking at the original: the last line is "Top comments (0)". It's likely a Dev.to comment section indicator. The user said "Reformat it into clean, readable Markdown for a technical audience." and "Output only the reformatted article body as Markdown, nothing else." I'll exclude it, as it's not part of the article's substantive content. But to be safe, I could include it as a separate line, but the rules say "Do not repeat the article title as a heading and do not wrap the whole answer in a code fence." It doesn't explicitly say to exclude trailing platform artifacts, but "reformatted article body" suggests I should focus on the article text. I'll omit "Top comments (0)" as it's not part of the article body content. However, if the user expects every line preserved, I might need to include it. But the instruction "Preserve every fact, name, number, and quotation exactly as given" doesn't necessarily mean every trailing line. I'll omit it, as it's likely not intended to be part of the reformatted article. I'll check if there's any other trailing stuff. The article body ends with "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. Top comments (0)". I'll end with the last substantive sentence, and not add "Top comments (0)". But if I look at the rules: "Preserve every fact, name, number, and quotation exactly as given. Never invent, add, remove, or reorder information." This is tricky. If "Top comments (0)" is part of the raw body, removing it would be removing information. But it's not a fact/name/number/quotation of the article; it's a platform comment count. The user said "Reformat it into clean, readable Markdown for a technical audience." I think it's safe to omit it, as it's not part of the article's content. However, to be absolutely safe, I could include it as a separate paragraph or note, but the user said "do not add ... any commentary of your own." Including it wouldn't be adding commentary, but it's also not part of the article. I'll omit it, as the article body logically ends with the project description. I'll re-read the user's instruction: "Output only the reformatted article body as Markdown, nothing else." I'll output only the article content, without the "Top comments (0)" line.
Now, check heading levels: I'm using ## headings. The rules say "Add Markdown section headings (## Heading) where the topic clearly shifts, so the article scans well." That's fine.
Ensure paragraphs are short and separated by blank lines. I'll make sure each section has one or more paragraphs, but kept short.
Check for inline code: I need to use backticks for code/inline identifiers. The rules: "Use bullet or numbered lists for enumerations, and inline code or fenced code blocks where code, commands, or identifiers appear." So I'll use inline code for build.py, data_*.py, python3 build.py, @cf/meta/m2m100-1.2b, ?v=, etc.
Let me review each paragraph and ensure inline code is used where appropriate.
Rewrite with inline code:
Overview and Purpose
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 plugreports 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. The interesting part (this is dev.to): the whole thing runs on Cloudflare's free tier. Here's how.
Architecture: Static Generation with a CMS
A dependency-free Python script <code>build.py</code> reads plain <code>data_*.py</code> files and emits ~1,100 static HTML pages - every drug profile, guide, comparison and data page - with full JSON-LD (MedicalWebPage, FAQPage, BreadcrumbList), honest lastmod sitemaps, and real bidirectional hreflang clusters. No framework, no build step beyond <code>python3 build.py</code>.
Wait, the rule says "inline code or fenced code blocks where code, commands, or identifiers appear." I can use inline code with backticks. In Markdown, inline code is text. I'll use single backticks for inline code. For readability, I'll do that.
But careful: The original has build.py with backticks in the user's message? Actually the user's message has ( build.py ) with spaces. I'll just use inline code markup with backticks. However, I need to ensure that the backticks don't interfere with the quotes or other formatting. I'll use backticks around identifiers.
Let me format inline code properly. In the final output, I'll write build.py, data_*.py, python3 build.py, etc.
Also, note: The original has _seo.js and hydrate.js. I'll use inline code for those.
Now, check the "What I'd do differently" section: It has bullet points. The rules say "Use bullet or numbered lists for enumerations." So bullet points are fine. I'll use - for bullets.
But note: The original has:
What I'd do differently - Never batch concurrent edits to the same source file. I corrupted the generator mid-session that way (6× duplicated content, and Python's last-definition-wins masked it). Recovery was raw.githubusercontent.com + the last known-good commit.
Comments
No comments yet. Start the discussion.