CSS 79.3%
Vue 17.4%
TypeScript 3.0%
JavaScript 0.3%
104 4 0

Clone this repository

https://tangled.org/finxol.io/blog
git@knot.finxol.io:finxol.io/blog

For self-hosted knots, clone URLs may differ based on your setup.

README.md

finxol blog#

This is the repo for finxol's blog.

All posts are in content/. Configuration is in blog.config.ts.

Technology stack#

  • Nuxt v4
  • Nuxt Content
  • TailwindCSS
  • Deno (Deploy EA)

Bluesky integration#

Tracking PR: #1

Comments on this blog are directly integrated with Bluesky, the atproto-based micro-blogging social network.

This integration relies on the @atcute/ library collection for interaction with Bluesky/atproto.

The idea was originally inspired from natalie's blog. Although I ended up using mostly the same tools and strategies, I didn't follow her post to build it here.

How it works in practice#

The author of the blog writes a post and publishes it. They can then post about it on Bluesky, find the post id, and add it to the bskyCid field in the post frontmatter. Any Bluesky post below the one identified will now be displayed at the bottom of the blog post, allowing for integrated conversation about the post.

How it works technically#

The AT Protocol is an open internet protocol for social applications. All the data is decentralised and public (for now). This openness allows us to reuse and build things based on that data very easily, in a built-in way, without hacky workarounds.

This integration works in several parts:

app/util/atproto.ts#

Contains the utility functions for retrieving all replies to a post, and extracting a post id from an atproto uri.

Uses @atcute/client to fetch using the app.bsky.feed.getPostThread RPC on the Bluesky public API. Everything is strongly typed, although fetch errors are handled as post not found to make handling simpler in the Vue component.

blog.config.ts#

The author DID is set blog-wide in the config file through authorDid, as it is primarily intended as a personal blog. If need be, I can always move the DID parameter to the post frontmatter, allowing for guest authors or secondary accounts too.

content.config.ts#

Since the Bluesky post CID needs to be set for each blog post independently, I added a bskyCid field in the post frontmatter.

app/components/BskyComments.vue#

This is the core component to display the replies.

The component simply fetches the replies by calling getBskyReplies, passing in the post CID passed as prop, and displays the content using the BskyPost component.

The reply, like, repost, and bookmark counts of the original Bluesky post are also displayed.

app/components/BskyPost.vue#

This component displays the post author, their avatar, the post content, and its stats beautifully.

Replies to replies are indented accordingly to visually thread replies together, using BskyPost recursively, with a MAX_DEPTH to set a limit to the number of replies to show.

app/pages/posts/[...slug].vue#

The actual post page only had some minor adjustments to integrate the BskyComments component, using a Suspense boundary with a fallback to avoid blocking the rendering of the actual content.

Others#

Some other files saw modifications, to adapt to this integration addition, allowing for visual consistency.

Advantages of the approach#

Since this blog is built with Nuxt, everything is SSRed. This makes the Bluesky integration a wonderful progressive enhancement. The comments will still display and show up as intended if the client has Javascript disabled, without blocking rendering of the actual content through the use of a Suspense boundary.

Using Bluesky as a comment platform allows me to integrate conversations about my posts directly alongside them, without bearing the load of moderation and user accounts.

Limitations#

As briefly mentioned, fetch errors are normalised to #notFoundPost, this could be refined for better reporting in the UI.

This integration also only handles plain text content. All embedded and rich media is effectively ignored for now.

Install locally#

# Install dependencies
pnpm i

# Run the development server
deno task dev

# Build for production
deno task build

# Deploy to Deno Deploy EA. Add `--prod` to deploy to production
deno deploy