I’ve been writing more consistently lately and figured it was time to add one to my blog.
Setting up the RSS feed
Astro makes RSS feeds pretty straightforward with their rss package.
We’ll start by installing it
bash
npm install @astrojs/rss
I’ll go ahead and create the rss endpoint
pages/rss.xml.ts
import rss from "@astrojs/rss";
import type { APIRoute } from "astro";
import { getCollection } from "astro:content";
export const GET: APIRoute = async ({ site }) => {
const blog = await getCollection("blog");
return rss({
trailingSlash: false,
stylesheet: "rss/pretty-feed.xsl", // for fancy styles
title: "Macawls' Blog",
site,
xmlns: {
media: "http://search.yahoo.com/mrss/", // media namespace
},
items: blog.map(({ data }) => ({
title: data.title,
pubDate: data.date,
description: data.subtitle,
link: `/blog/${data.slug}`,
categories: data.tags,
author: "josh@macawls.dev",
customData: `<media:thumbnail url="${site}/api/posts/thumbnails/${data.slug}" width="600" height="315" />`,
})),
});
};
Thumbnail Endpoint (Optional)
I’m using the Astro Assets API to optimize my source images. I write all of my posts in MDX - that makes things tricker than simply using an image at a public path.
There’s definitely room for optimization here - I’m going to keep it simple.
pages/api/posts/thumbnails/[slug].ts
import type { APIRoute } from "astro";
import { getImage } from "astro:assets";
import { getCollection } from "astro:content";
export const GET: APIRoute = async ({ params, request }) => {
const { slug } = params;
if (!slug) {
return new Response("Slug is required", {
status: 400,
});
}
const posts = await getCollection("blog");
const post = posts.find((p) => p.data.slug === slug);
if (!post) {
return new Response("Post not found", {
status: 404,
});
}
const ogImage = await getImage({
src: post.data.thumbnail,
width: 600,
height: 315,
format: "webp",
});
const imageResponse = await fetch(new URL(ogImage.src, request.url));
const imageBuffer = await imageResponse.arrayBuffer();
return new Response(imageBuffer, {
status: 200,
headers: {
"Content-Type": "image/webp",
"Cache-Control": "public, max-age=2592000", // 30 days
},
});
};
Styling
In order to make it look somewhat consistent with my design, I used Pretty Feed as a base for the design, and politely asked v0 to do some simple styling.
Automating with Dokploy and FeedCord
I wanted to automatically announce new posts to a community discord server I’m currently building.
I stumbled upon FeedCord which is a service that monitors RSS feeds with polling.
yml
services:
feedcord:
image: qolors/feedcord:latest
restart: unless-stopped
volumes:
- ../files/appsettings.json:/app/config/appsettings.json
Wrapping up
I am Gen Z 😅 I’ve never used RSS in my life - this was an interesting learning experience.
I do still want to implement web hooks (create, update, delete), which is why I migrated to hashnode a while ago. There are pros and cons to everything and I do not want to give up the flexibility of MDX. That’ll be in the back burner for now.
The whole thing took maybe an hour and a bit to set up(mostly reading docs), and now I don’t have to think about it anymore. My RSS feed is live at /rss.xml if you want to subscribe 📡
Feel free to check out the FeedCord if you want to set up something similar for your own blog.