Andy Jarrett // Code. Develop. Create.

Creating an Alpine.js RSS Reader with my Bluesky Profile feed

Developer debugging
Photo by Alex-David Ile on Unsplash

Unlike Twitter, Bluesky allows you to get a RSS feed of your profile. Which is pretty cool as RSS is a great way to play to pulling in data from an external source when you are learning any code. Sure its no JSON but we can handle that another day.

In this post, I am creating a simple single-page application (SPA) using Alpine.js that pulls in an RSS feed from my Bluesky profile. With the growing interest in decentralised social networks like Bluesky, especially as Twitter reinstates previously banned accounts, this approach offers a fresh alternative for staying connected.

I'll also cover why I'm using the free proxy service AllOrigins, which I find helpful for fetching these feeds.

Here is the HTML which is straight forward, and commented for reference:

<div class="container">

  <!-- Initialise the Alpine.js component with data from rssFeed() function -->
  <!-- Automatically fetch the feed when the component is initialised -->
  <div x-data="rssFeed()" x-init="fetchFeed()">

    <!-- Header for the RSS reader application -->
    <h1>Alpine.js RSS Reader Parsing Bluesky User Feed</h1>

    <!-- Display a loading message while the feed is being fetched -->
    <div x-show="loading">Loading...</div>

    <!-- Display an error message if fetching the feed fails -->
    <div x-show="!loading && error" class="error">
      <p>Error loading feed: <span x-text="error"></span></p>
    </div>
    <div class="row justify-content-center">
      <div class="col-md-6 col-sm-12">

        <!-- Display the feed items once loading is complete and no errors occurred -->
        <div x-show="!loading && !error">

          <!-- Loop through each item in the feedItems array and create a card for each -->
          <template x-for="item in feedItems" :key="item.link">
            <div class="card border-primary mb-3">

              <!-- Display the description of the current feed item -->
              <div class="card-body">
                <p class="card-text" x-text="item.description"></p>
              </div>

              <!-- Display the publication date and link to the original post -->
              <div class="card-footer text-body-secondary">
                Posted: <a :href="item.link" target="_blank" x-text="formatDate(item.pubDate)"></a>
              </div>
            </div>
          </template>
        </div>
      </div>
    </div>
  </div>
</div>

There alpine.js script is straight forward as well.

<script>
  // Function to manage RSS feed data and logic
  function rssFeed() {
    return {
      feedItems: [], // Array to store the parsed RSS feed items
      loading: true, // Boolean to track the loading state
      error: null, // Variable to store any error messages

      // Async function to fetch and process the RSS feed
      async fetchFeed() {
        // URL for the RSS feed, fetched via AllOrigins proxy to bypass CORS issues
        const feedUrl = `https://api.allorigins.win/get?url=${encodeURIComponent('https://bsky.app/profile/did:plc:bd3g6lum2orsqnolbu4urvxj/rss')}`;
        try {
          // Fetch the RSS feed using the AllOrigins API
          const response = await fetch(feedUrl);
          // Parse the response as JSON
          const data = await response.json();
          // Use DOMParser to convert the RSS feed from XML to a DOM object
          const parser = new DOMParser();
          const xml = parser.parseFromString(data.contents, 'text/xml');
          // Select all  elements from the RSS feed
          const items = xml.querySelectorAll('item');
          // Map over each item to extract the relevant data (link, publication date, description)
          this.feedItems = Array.from(items).map(item => ({
            link: item.querySelector('link').textContent, // Extract the link to the full post
            pubDate: item.querySelector('pubDate').textContent, // Extract the publication date
            description: item.querySelector('description').textContent // Extract the description
          }));
        } catch (error) {
          // Handle errors by setting the error message and logging the error
          this.error = 'Failed to load the feed. Please try again later.';
          console.error(error.message);
        } finally {
          // Set loading to false once the fetch operation is complete, whether successful or not
          this.loading = false;
        }
      },

      // A quick function to format the publication date in a readable format
      formatDate(dateString) {
        const options = { year: 'numeric', month: 'long', day: 'numeric', hour: 'numeric', minute: 'numeric', second: 'numeric', timeZoneName: 'short' };
        const date = new Date(dateString);
        return date.toLocaleDateString(undefined, options);
      }
    };
  }
</script>

The Role of AllOrigins as a Proxy in the above code

When trying to fetch RSS feeds directly from a different domain (in my case, Bluesky), you might encounter cross-origin resource sharing (CORS) issues. This is where AllOrigins comes in handy to know/use. It acts as a proxy that fetches the content for you, bypassing CORS restrictions and delivering the data without errors.

Thats it. By combining Alpine.js, Bluesky's RSS feeds, and AllOrigins, you can build a simple yet effective SPA that displays the latest content from any Bluesky profile.

Come and find me on BlueSky at bsky.app/profile/andyjarrett.com

You can find this code on Github, without all the comments, with my plan to add HTMX as well to load in the "tweets"

I’m here, learning and working away. If you liked this content and want to keep me going, consider buying me a coffee. Your support keeps this site running and the coffee brewing! ☕️