Introduction
Do you have a Mastodon profile and a Gatsby site? If so, you might be interested in integrating your Mastodon profile's RSS feed with your Gatsby site. This feature can automatically display your latest Mastodon posts on your site, keeping your content fresh and engaging for your visitors. In this post, I'll walk you through the technical details of how I integrated my Mastodon profile's RSS feed with my static Gatsby site, and explain why this feature is valuable for any Mastodon user with a website.
Before we get into the technical details though, here are some reasons why integrating your profile's RSS feed with your site can be valuable:
- Keeps your site content fresh: By integrating your Mastodon profile's RSS feed with your Gatsby site, you can automatically display your latest Mastodon posts on your site without having to manually update your site content. This can keep your site looking fresh and up-to-date.
- Increases engagement: By displaying your Mastodon posts on your Gatsby site, you give your visitors another way to engage with you and your content. They can read your posts and maybe even follow you on Mastodon.
- Enhances your site's value: By offering more content and engagement opportunities, you increase the overall value of your site to your visitors. This can lead to increased traffic, longer site visits, and even more followers on Mastodon.
- Demonstrates your expertise: If your Mastodon posts are related to your niche or industry, displaying them on your Gatsby site can demonstrate your expertise and thought leadership in that area. This can help you build a following and establish yourself as a credible voice in your field.
- Saves time and effort: Finally, integrating your Mastodon profile's RSS feed with your Gatsby site can save you time and effort in the long run. Once the integration is set up, your Mastodon posts will be automatically displayed on your site without any additional work from you. This can free up your time to focus on other aspects of your site or your business.
The Technical Details of Integrating the RSS Feed
First, I need to briefly mention how to get a profile's RSS feed. I found a post on Mastodon which summarizes this nicely. All you have to do is add ".rss" to the end of any profile's public URL. For example, mine is https://mastodon.social/@logarithmicspirals.rss. The next step is going to be actually fetching the data from the feed from the client.
What helped me get started was first looking up ways you can fetch and process an RSS feed using JavaScript. While I did find packages and intricate solutions to this, the CSS-Tricks website has a useful tutorial on how to fetch/parse RSS feeds with plain JavaScript. To summarize the tutorial, you can do it all using the Fetch API.
Now, for those who aren't aware, Gatsby gives developers the ability to perform build time and client runtime data fetching. While it would be possible to fetch the RSS feed at build time, that would limit us in how often and easily the data is refreshed on the page. My site is set up in such a way that builds only occur when I'm making changes to its source code. So that means Mastodon content could get stale if I haven't pushed a build in a while. The better choice is to fetch the data from the client side. Thankfully, Gatsby's page on data fetching has a section specifically about using the fetch API. To summarize that section, one can use a combination of useState
and useEffect
to get the job done. We'll write our fetch code inside the useEffect
function and then leverage useState
to get that data to the React components which create the rendered content. This way, when people visit the page, the content is refreshed and the latest posts have a chance to appear.
First Attempt
Here's what the initial attempt at fetching and processing the data looked like:
const mastodonRssUrl = "https://mastodon.social/@logarithmicspirals.rss"
// ...
const [mastodonPosts, setMastodonPosts] = useState([])
useEffect(() => {
fetch(mastodonRssUrl)
.then(response => response.text())
.then(str => new window.DOMParser().parseFromString(str, "text/xml"))
.then(data => {
const items = Array.from(data.querySelectorAll("item")).map(item => {
return {
link: item.querySelector("link").textContent,
pubDate: item.querySelector("pubDate").textContent,
description: sanitizeHtml(
item.querySelector("description").textContent
),
}
})
setMastodonPosts(items)
})
}, [])
Looking at this, it seems fine. What I'm doing is fetching the RSS feed, converting the node list into an array, and then mapping that array into an array of custom objects. The custom objects are what will be used to create the page through the useState
hook. Now, let's try to use this data to create the page. In the below code, I'll skip explanation for how the CSS classes were chosen to just focus on the logic of how it works:
<div className="column">
<h2 className="title is-2 has-text-centered">Mastodon Posts</h2>
<ol className="no-list-style">
{mastodonPosts.map((post, index) => {
const date = new Date(post.pubDate)
return (
<li key={index} className="card mb-5">
<div className="card-content">
<p
className="mb-4"
dangerouslySetInnerHTML={{ __html: post.description }}
/>
<i>{`Published on ${
months[date.getMonth()]
} ${date.getDate()}, ${date.getFullYear()}`}</i>
</div>
<div className="card-footer">
<p className="card-footer-item">
<span>
View on{" "}
<a
href={post.link}
aria-label="mastodon-post"
target="_blank"
rel="noreferrer"
>
Mastodon
</a>
</span>
</p>
</div>
</li>
)
})}
</ol>
</div>
The mastodon posts are used to create JSX components from the RSS feed data. Make note of two specific pieces:
- Using
dangerouslySetInnerHTML
to add the actual content of the post into the page. - Using the individual pieces of the date to create a string.
These two specific pieces require a bit of extra attention.
Sanitizing HTML Before Displaying It on the Page
After writing the code to add inject the content from the RSS feed into the page, I stopped to think about what it's actually doing. Ultimately, the data comes from a third party which I don't have control over. To be safe, it would be better to sanitize the HTML received from the feed before displaying it on the page. Gatsby actually has a nice article on security where they talk about preventing attacks from cross-site scripting. The solution is to use a package like sanitize-html to, as the name suggests, sanitize HTML before displaying it on your page.
To add it to the site, all I had to do was install it:
npm i sanitize-html
Then integrate it with the code used to create the page:
// ...
import sanitizeHtml from "sanitize-html"
// ...
useEffect(() => {
fetch(mastodonRssUrl)
.then(response => response.text())
.then(str => new window.DOMParser().parseFromString(str, "text/xml"))
.then(data => {
const items = Array.from(data.querySelectorAll("item")).map(item => {
return {
link: item.querySelector("link").textContent,
pubDate: item.querySelector("pubDate").textContent,
description: sanitizeHtml(
item.querySelector("description").textContent
),
}
})
setMastodonPosts(items)
})
}, [])
Formatting the RSS Date Field and Final Result
When looking at the RSS data, there's a pubDate
field for each item. For example, you might see a date like this show up in that field Sun, 26 Feb 2023 09:15:11 +0000
. To format it, I found a helpful Stack Overflow answer. So now what I've ended up with is the following:
const mastodonRssUrl = "https://mastodon.social/@logarithmicspirals.rss"
// Months array comes from https://stackoverflow.com/a/5452453
const months = [
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December",
]
// ...
useEffect(() => {
fetch(mastodonRssUrl)
.then(response => response.text())
.then(str => new window.DOMParser().parseFromString(str, "text/xml"))
.then(data => {
const items = Array.from(data.querySelectorAll("item")).map(item => {
return {
link: item.querySelector("link").textContent,
pubDate: item.querySelector("pubDate").textContent,
description: sanitizeHtml(
item.querySelector("description").textContent
),
}
})
setMastodonPosts(items)
})
}, [])
// ...
<div className="column">
<h2 className="title is-2 has-text-centered">Mastodon Posts</h2>
<ol className="no-list-style">
{mastodonPosts.map((post, index) => {
const date = new Date(post.pubDate)
return (
<li key={index} className="card mb-5">
<div className="card-content">
<p
className="mb-4"
dangerouslySetInnerHTML={{ __html: post.description }}
/>
<i>{`Published on ${
months[date.getMonth()]
} ${date.getDate()}, ${date.getFullYear()}`}</i>
</div>
<div className="card-footer">
<p className="card-footer-item">
<span>
View on{" "}
<a
href={post.link}
aria-label="mastodon-post"
target="_blank"
rel="noreferrer"
>
Mastodon
</a>
</span>
</p>
</div>
</li>
)
})}
</ol>
</div>
Now when people visit my homepage, they'll see my blog posts on the left and my Mastodon posts on the right.
Iterative Development and Future Improvements
For this feature, I'm taking an iterative approach, which means that I'll be continuously making improvements to it over time. There are several improvements that I plan to make, including:
- Making the feed update without having to reload the page - this will make the user experience smoother and faster, as they won't have to wait for the page to reload every time they want to see new posts. Probably what I'll do is have the fetch occur every 30 seconds after the page loads.
- Using a placeholder to reserve the space on the page until the content loads - this will prevent the page from shifting around while the posts load, which can be distracting for users. I've started looking into this, and placeholder-loading looks like a promising package to get the job done.
- Adding a limiter as the number of posts grows - as more and more posts are added to the feed, it will become important to limit the number of posts that are displayed at any one time. This will ensure that the page remains fast and responsive, even as the feed grows.
- Integrating images - eventually, I would like to add support for images in the feed, as this will make it more visually appealing and engaging.
- Developing a component for displaying rich links from other websites - when users include links to other websites in their posts, it would be great to be able to display a rich link with text and images, similar to what Mastodon and Twitter do. This will make the feed more interesting and informative for users.
Out of these improvements, I think the one I should tackle first is the placeholder. Having a placeholder for when the page is initially loaded will create a better user experience, as users will know that something is coming and won't be left staring at a blank page. I also think it will be relatively straightforward to implement.
The most complicated improvement will most likely be the development of a component for parsing links from other websites. The idea will be to parse a linked page's metadata to create a rich link with text and images. While this will be challenging, I think it will be an interesting and valuable addition to the feed. In fact, I believe it will be complex and interesting enough to warrant its own blog post in the future, so I can document the process and share my learnings with others.
Conclusion
After exploring the technical details of integrating a Mastodon profile's RSS feed with a Gatsby site, I highly recommend this feature to Mastodon users with websites. Not only does it keep your site content fresh, but it also increases engagement, enhances your site's value, demonstrates your expertise, and saves time and effort in the long run.
While it may seem daunting at first, fetching and processing the RSS feed using JavaScript is actually quite simple with the help of resources like the CSS-Tricks tutorial. And with Gatsby's page on data fetching, you can easily retrieve the Mastodon content from the client side and use it to create dynamic and up-to-date content for your visitors.
Overall, integrating a Mastodon profile's RSS feed with a Gatsby site is a valuable and straightforward way to add more content and engagement opportunities to your website while saving time and effort in the long run. Give it a try and see the benefits for yourself!
To see this feature in action, visit my homepage.