Building a Bluesky Comments Web Component: Bringing Conversations to Life

Published 1 Nov 2024

How I built a lightweight, framework-agnostic web component for embedding Bluesky comments on static sites.

In the ever-evolving world of social media, Bluesky has emerged as an exciting decentralized alternative to traditional platforms. As a developer passionate about creating interactive web experiences, I set out to build a custom web component that could seamlessly integrate Bluesky post comments into any website.

Credits

This project was started by Emily Liu, who came up with the idea of using a Bluesky thread as a comments thread. Cory Zue expanded on the idea by removing lots of NextJS dependencies. However it still required a lot of React dependencies, which was why I started creating a web component based version. Lots of developers use static sites for blogs, this works great for static sites, reducing the JS size by 98.5%!

The Motivation

Web components offer a powerful way to create reusable, encapsulated UI elements. My goal was to create a simple, lightweight component that could:

  • Fetch comments for a specific Bluesky post
  • Render comments in a clean, readable format
  • Provide a customizable and lightweight solution for embedding discussions

Key Technical Challenges

1. Fetching Comments via Bluesky's Public API

The first hurdle was interfacing with Bluesky's public API. The fetchThread method became the cornerstone of the component:

async fetchThread(uri) {
  const url = `https://public.api.bsky.app/xrpc/app.bsky.feed.getPostThread?uri=${encodeURIComponent(uri)}`;
  
  const response = await fetch(url, {
    method: "GET",
    headers: { Accept: "application/json" },
    cache: "no-store"
  });

  if (!response.ok) {
    throw new Error(`Failed to fetch thread: ${response.statusText}`);
  }

  return await response.json();
}

2. Creating a Flexible Rendering Approach

The render method had to be both powerful and flexible. I implemented a sorting mechanism to prioritize high-engagement comments and added a "Show more" functionality:

render() {
  const sortedReplies = this.thread.replies.sort(
    (a, b) => (b.post.likeCount ?? 0) - (a.post.likeCount ?? 0)
  );

  // Render initial comments and provide expand functionality
  sortedReplies.slice(0, this.visibleCount).forEach((reply) => {
    commentsContainer.appendChild(this.createCommentElement(reply));
  });
}

3. Security and Styling Considerations

I implemented several key features to ensure security and flexibility:

  • HTML escaping to prevent XSS attacks
  • CSS custom properties for easy theming
  • Shadow DOM for style encapsulation

Lessons Learned

  • API Interaction: Working with public APIs requires robust error handling and flexible parsing.
  • Web Components: They provide an elegant way to create reusable, framework-agnostic UI elements.
  • Performance: Implementing features like lazy loading helps manage larger comment threads.

Code Highlights

The component uses several modern web APIs:

  • CustomElementRegistry for defining the web component
  • Shadow DOM for style encapsulation
  • fetch for API interactions

Customization Options

The component supports several customization methods:

  • no-css attribute to disable default styling
  • CSS custom properties for theming
  • Configurable number of initial comments

Future Improvements

While the current implementation is functional, there's room for enhancement:

  • Add date/timestamp to comments
  • Implement nested comment threading
  • Add user interaction features like liking or replying

Conclusion

Building this Bluesky comments web component was an exciting journey into modern web development. It demonstrates the power of web components in creating modular, reusable UI elements that can work across different frameworks and websites.

Usage Example

<bsky-comments post="at://did:plc:user/collection/postid"></bsky-comments>

By leveraging web standards and Bluesky's public API, we've created a simple yet powerful way to embed social discussions into any web page.