Adding an RSS Feed to my Blog

How to setup an RSS feed with automated Discord notifications 🚀

javascriptweb developmenttutorial
Adding an RSS Feed to my Blog

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.

Not found

yml

services:
  feedcord:
    image: qolors/feedcord:latest
    restart: unless-stopped
    volumes:
      - ../files/appsettings.json:/app/config/appsettings.json

discord screenshot

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.

thumbs-up-kid-meme

CC BY-SA 4.0 by Joshua Macauley