Creating an Alpine.js RSS Reader with my Bluesky Profile feed
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"