{
"$type": "site.standard.document",
"bskyPostRef": {
"cid": "bafyreifeg4vg4darvinvtk7uzedj3t2jebinozgbmforbycp7uok2kr4iq",
"uri": "at://did:plc:jbot5ew44m7z5p6gqeasjf2z/app.bsky.feed.post/3mlum3lca4i24"
},
"coverImage": {
"$type": "blob",
"ref": {
"$link": "bafkreibotxt463udgfs2i3naetpvpnupb6mleiqwmdknwr57q3hgrob6mi"
},
"mimeType": "image/png",
"size": 42107
},
"description": "feat: syndicate Astro posts to ATProto via standard.site and Bookhive",
"path": "/posts/pushing-posts-to-the-atmosphere/",
"publishedAt": "2026-05-14T00:00:00.000Z",
"site": "at://did:plc:jbot5ew44m7z5p6gqeasjf2z/site.standard.publication/3mltm3qiqssh2",
"tags": [
"Code"
],
"textContent": "A few weeks ago I wired up comments so replies on Bluesky would show up under each post here. That worked, but it only covered conversations.\n\nWhat I wanted next was for the blog itself to be discoverable through ATProto-native readers, without moving off Astro. The shape of that idea is POSSE, short for \"publish on your own site, syndicate elsewhere.\" The blog stays canonical, and other places that surface my posts read from it.\n\ntl;dr: I write here, then run three small scripts to syndicate the post: one creates a Bluesky thread, one mirrors it to my PDS as a standard.site document, and (for book reviews) one writes a Bookhive record. The post stays the canonical version. Everything else points back.\n\n \n\nDomain as identity\n\nThe foundation is making the blog's domain my ATProto handle. I'd already pointed at my DID with a DNS TXT record. To make that resolvable from the blog itself, I dropped a single file at :\n\nNow on Bluesky is verifiable from the site, and ATProto clients that resolve handles via well-known will pick it up.\n\n \n\nstandard.site documents\n\nstandard.site is a community-maintained set of ATProto lexicons for long-form writing. Publish records that follow them and ATProto-native readers like Pckt and Leaflet can index your posts even though they live on your own site.\n\nI originally used [](https://www.npmjs.com/package/@bryanguffey/astro-standard-site) for this. The current version keeps a small local wrapper around [](https://www.npmjs.com/package/@atproto/api), mostly so the record shape and content transform live in this repo. The setup is still two scripts.\n\nThe first runs once to create a record on my PDS:\n\nThe publication rkey lives in , which the site config and the scripts both import. The second script walks , skips drafts, and writes each post as a record:\n\nA small Astro endpoint at serves the publication AT URI as so readers can verify ownership.\n\nThe first sync wrote 32 documents to my PDS in a single pass. Re-running the script is safe because the publisher derives a deterministic rkey from each slug, so existing records get updated instead of duplicated.\n\n \n\nBookhive book reviews\n\nBookhive is an ATProto app for tracking books. You write reviews, rate things, log what you're reading, and it all ends up in your PDS as records. I've written a few posts about books I've read (tagged , titled ), and I wanted those to show up in Bookhive without doubling my writing workflow. The book reviews were also a good excuse to see how syndication feels when the downstream is another ATProto app instead of a feed.\n\nThe script reads my blog, picks anything tagged , parses out of the post title, looks up the book against Bookhive's XRPC to get a stable , then writes a record on my PDS pointing at the canonical review URL.\n\nAll seven of my book posts showed up on my Bookhive profile after the first run.\n\nThere's probably a better way to do this than parsing out of the post title. A dedicated block in the frontmatter would be cleaner, since it could carry the ISBN and author directly without depending on a naming convention. But the parsing approach is good enough for v1.\n\n \n\nThree commands, one at a time\n\nThe three scripts (Bluesky thread, standard.site document, Bookhive record) all read the same and from , so each one is a quick npm command. My first cut at this wrapped them into a single script, and each of them took an optional flag so I could backfill the existing posts on a first run. That worked well exactly once, and then bit me.\n\nIt bit me a few weeks later, when I made a small refactor to . I ran the script to test it on one new post, and forgot that was still the default code path I was exercising. It happily created a fresh Bluesky thread for every post in the archive without a . Thirty-one new posts hit my feed in about ten seconds. I deleted them, apologized to the timeline, and went back to look at the script.\n\nThe fix was to take the foot-guns out. is gone from both and . Each script now requires an explicit slug. The wrapper is gone too, because the convenience of one command was exactly what made the mistake so easy.\n\nThe current publish workflow:\n\nMore typing, but each step is a thing I have to mean. The deploy happens on the first push, the comments thread shows up after the second.\n\nThere's probably a better way to wire this up (a Netlify build hook, a git post-push, something), but the local approach is good enough for now. The step writes back to the post's frontmatter, and I'd prefer not to have a remote machine making commits to my repo.\n\n:::callout{type=\"update\"}\nUpdate (2026-05-28): Book posts now carry a block in frontmatter (, , ) so skips the searchBooks lookup. For new book posts, add one step to the workflow above: after committing the post, before .\n:::\n\n \n\nWhat I like about this\n\nNothing downstream is load-bearing. If Pckt redesigns or Bookhive disappears, the records I've already written are still in my PDS and the post itself reads exactly the same here. Syndication is additive.\n\nThe other thing I keep noticing is that my handle is the same in every one of these places. Same on Bluesky, Bookhive, and Pckt, all pointing back here. The web stopped feeling like that for a while; it's nice to have it back in a small corner.\n\n \n\nResources\nPOSSE on IndieWeb\nstandard.site lexicons\n[](https://www.npmjs.com/package/@atproto/api)\nBookhive\nPckt\nLeaflet\nAdding Bluesky comments to an Astro blog",
"title": "Pushing Posts to the Atmosphere"
}