The standard answer for discussion on a static site is: pick a third-party comments service, embed it, and manage all of that hassle. I didn’t really want to do that, not because it’s wrong, but because I thought having the conversation extend to Bluesky and Mastodon would be a bit more fun. So I just wanted to link it back to the post.
So I built a script that handles this all for me automatically.
What it does
When a new post merges to main and deploys, a GitHub Actions workflow fires. It runs scripts/announce.mjs, which:
- Checks
git difffor newly added.mdxfiles insrc/content/blog/andsrc/content/projects/ - Parses the frontmatter to get the title, excerpt, and image
- Posts to Bluesky and Mastodon with the cover image attached if one exists
- Takes the social post URLs from the API responses
- Writes those URLs back into the file’s frontmatter as
blueskyUrlandmastodonUrl - Commits and pushes the updated file
On the post page, I check for those frontmatter fields and render discussion icon links at the bottom. If neither URL exists, usually old posts and drafts, the footer just doesn’t show up.
The loop problem
The commit-and-push at the end of the script triggers the Deploy workflow, which would trigger Announce again. Infinite loop.
The fix is a single condition on the Announce workflow:
if: |
github.event.workflow_run.conclusion == 'success' &&
github.event.workflow_run.actor.login != 'github-actions[bot]'
If the push came from the bot, skip. One pass only.
Why not a comments system
There are solid options for this, like giscus uses GitHub Discussions, utterances uses issues, Disqus is Disqus. They work. But they add a third-party embed to every page, and the discussion ends up in a widget and, who knows if anyone will even look at those anyway.
The social feed is where it feels natural to continue the conversation. This just makes that explicit, and links it back from the post so it’s easy to find.
One edge to know about
The script fetches the cover image from the live site before uploading it to each platform. That means there’s a small window after deploy where the image might not be up yet. In practice the image has always been available by the time the announce step runs, but it’s a soft dependency worth knowing about.
Replies on Bluesky
Loading replies…