# 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](https://tangled.org/finxol.io/blog/pulls/1/) and [#3](https://tangled.org/@finxol.io/blog/pulls/3) 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](https://natalie.sh/posts/bluesky-comments/). 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](https://atproto.com/) is an open internet protocol for social applications. All the data is decentralised and public ([for now](https://pfrazee.leaflet.pub/3lzhui2zbxk2b)). 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 ```sh # 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 ```