1# finxol blog 2 3This is the repo for finxol's blog. 4 5All posts are in `content/`. 6Configuration is in `blog.config.ts`. 7 8## Technology stack 9 10- Nuxt v4 11- Nuxt Content 12- TailwindCSS 13- Deno (Deploy EA) 14 15## Bluesky integration 16 17Tracking PR: [#1](https://tangled.org/finxol.io/blog/pulls/1/) and [#3](https://tangled.org/@finxol.io/blog/pulls/3) 18 19Comments on this blog are directly integrated with Bluesky, the atproto-based micro-blogging social network. 20 21This integration relies on the `@atcute/` library collection for interaction with Bluesky/atproto. 22 23The idea was originally inspired from [natalie's blog](https://natalie.sh/posts/bluesky-comments/). 24Although I ended up using mostly the same tools and strategies, I didn't follow her post to build it here. 25 26### How it works in practice 27 28The author of the blog writes a post and publishes it. 29They can then post about it on Bluesky, find the post id, and add it to the `bskyCid` field in the post frontmatter. 30Any Bluesky post below the one identified will now be displayed at the bottom of the blog post, allowing for integrated conversation about the post. 31 32### How it works technically 33 34The [AT Protocol](https://atproto.com/) is an open internet protocol for social applications. 35All the data is decentralised and public ([for now](https://pfrazee.leaflet.pub/3lzhui2zbxk2b)). 36This openness allows us to reuse and build things based on that data very easily, in a built-in way, without hacky workarounds. 37 38This integration works in several parts: 39 40#### `app/util/atproto.ts` 41 42Contains the utility functions for retrieving all replies to a post, and extracting a post id from an atproto uri. 43 44Uses `@atcute/client` to fetch using the `app.bsky.feed.getPostThread` RPC on the Bluesky public API. 45Everything is strongly typed, although fetch errors are handled as `post not found` to make handling simpler in the Vue component. 46 47#### `blog.config.ts` 48 49The author DID is set blog-wide in the config file through `authorDid`, as it is primarily intended as a personal blog. 50If need be, I can always move the DID parameter to the post frontmatter, allowing for guest authors or secondary accounts too. 51 52#### `content.config.ts` 53 54Since the Bluesky post CID needs to be set for each blog post independently, 55I added a `bskyCid` field in the post frontmatter. 56 57#### `app/components/BskyComments.vue` 58 59This is the core component to display the replies. 60 61The component simply fetches the replies by calling `getBskyReplies`, passing in the post CID passed as prop, 62and displays the content using the `BskyPost` component. 63 64The reply, like, repost, and bookmark counts of the original Bluesky post are also displayed. 65 66 67#### `app/components/BskyPost.vue` 68 69This component displays the post author, their avatar, the post content, and its stats beautifully. 70 71Replies to replies are indented accordingly to visually thread replies together, using `BskyPost` recursively, 72with a `MAX_DEPTH` to set a limit to the number of replies to show. 73 74 75#### `app/pages/posts/[...slug].vue` 76 77The actual post page only had some minor adjustments to integrate the `BskyComments` component, 78using a `Suspense` boundary with a fallback to avoid blocking the rendering of the actual content. 79 80#### Others 81 82Some other files saw modifications, to adapt to this integration addition, allowing for visual consistency. 83 84### Advantages of the approach 85 86Since this blog is built with Nuxt, everything is SSRed. 87This makes the Bluesky integration a wonderful progressive enhancement. 88The comments will still display and show up as intended if the client has Javascript disabled, 89without blocking rendering of the actual content through the use of a `Suspense` boundary. 90 91Using Bluesky as a comment platform allows me to integrate conversations about my posts directly alongside them, 92without bearing the load of moderation and user accounts. 93 94### Limitations 95 96As briefly mentioned, fetch errors are normalised to `#notFoundPost`, 97this could be refined for better reporting in the UI. 98 99This integration also only handles plain text content. 100All embedded and rich media is effectively ignored for now. 101 102## Install locally 103 104```sh 105# Install dependencies 106pnpm i 107 108# Run the development server 109deno task dev 110 111# Build for production 112deno task build 113 114# Deploy to Deno Deploy EA. Add `--prod` to deploy to production 115deno deploy 116```