-
@ Andrew G. Stanton
2025-06-10 00:13:51How I Built a Decentralized CMS with Nostr + GitHub Actions – Part 2
Refinements, resilience, and real-world publishing
In Part 1, I shared the first steps of building a publishing stack that puts authors in control — using Nostr for content, GitHub Actions for automation, and GitHub Pages for free, fast hosting.
But that was just the foundation.
In this second installment, I focused on improving the system with better data hygiene, styling consistency, and end-to-end automation — all while keeping things minimal and open-source.
Here’s what changed — and what’s coming next.
✅ Key Improvements (Week of June 8)
🔁 1. Deduplication across relays
Nostr relays often echo the same events. In the original version, my script could pull the same article multiple times.\ To fix that, I updated the
fetch_articles.py
script to:-
Gather events from 8–10 relays concurrently using
asyncio
-
Flatten the results
-
Deduplicate based on
event.id
This brought the number of unique articles down from 90+ to around 20 — a huge cleanup win.
🏷 2. Tag styling and wrapping
Previously, the tags were unstyled or ran off the edge of the container. Now:
-
Tags wrap cleanly and align left
-
Each tag has a soft background and spacing (
#tag
) -
Tags are parsed directly from Nostr
["t", ...]
tags
It looks better. It feels more coherent. And it reflects the decentralized ethos — clean, not corporate.
📅 3. Proper
published_at
parsingNostr events can include a
["published_at", timestamp]
tag. Originally, my site sorted and displayed articles by theircreated_at
time — which was sometimes misleading.Now, the script:
-
Extracts
published_at
(if present) -
Falls back to
created_at
if missing -
Uses this value for both sorting and display in
index.html
This way, articles appear in the correct order — based on when I intended to publish them.
🎨 4. Markdown cleanup and layout polish
Markdown content from Nostr sometimes included extra backslashes (
\\
) or unparsed characters. I updated the rendering logic to:-
Remove unnecessary escape characters
-
Use consistent fonts, spacing, and margins
-
Inherit the overall styling from stantonweb.com
Now the articles don’t just work — they feel native to the rest of my site.
🤖 5. GitHub Actions automation
This was a big one: enabling daily auto-publishing.
I created a GitHub Actions workflow that:
-
Pulls all my
kind:30023
long-form articles daily from Nostr relays -
Deduplicates and filters by tag
-
Generates one
.html
file per article and updates theindex.json
-
Commits the changes back to GitHub Pages — no manual pushing needed
After some token setup troubleshooting (and a wrong
known_hosts
key 😅), it works beautifully.You can still run the script manually via:
bash
CopyEdit
./fetch_articles.sh
But now, it’s fire-and-forget. Just post on Nostr — the rest happens automatically.
⏱ Total Time Spent (est. 15 hours)
-
Saturday (\~5h): initial build, tried sync-based fetch logic
-
Sunday (\~5h): rewrote with
asyncio
, deduplication, cleaned up article objects -
Monday (\~5h): full styling pass, timestamp fixes, token creation, and automation pipeline setup
All running with zero paid infrastructure.
🔜 Coming Soon
-
🗂 Pagination: View 10–50 articles per page
-
🗓 Monthly Archives: Top-level by Year → then Month → then article list
-
🧠 RSS Feed: Auto-generate RSS from my
npub
so any Nostr long-form post becomes a blog feed\ (Bonus: use it to follow others via Primal or standard readers)
🙌 Built With
-
Nostr (Primal, Damus, etc.)
-
GitHub Pages + Actions
-
Python (asyncio, websockets)
-
A little stubbornness
-
A lot of help from ChatGPT (“Dr. C”) 🧠
💡 Why This Matters
Most people don’t own their publishing tools.\ Medium can unlist you. Substack can shadowban. Even Ghost requires trust in a hosted instance.
But with Nostr + GitHub, I control the data, the visibility, and the publishing logic.
That’s the future I want — for myself, for others, for truth.
One article at a time.
🟧 View live:\ https://andrewgstanton.github.io/blog-stantonweb-site\ 📬 Zap: https://tinyurl.com/yuyu2b9t
-