keith is
🖥️ working on blog

Adding a RSS feed to a RemixJS 1.x blog

Hey there! My blog no longer uses remix, and this post only applies to Remix 1, and I 🤞 hope you've upgraded.

One of the reasons I chose Remix for this site was the ability to add server-side code easily to any route, or even to have no react components returned at all. It kind of reminds me of PHP, or rails! And so I’m able to add a RSS feed to this blog without having to do much extra work.

Resource Route

Remix has a feature called resource routes that let you create a page that only responds from the server with data, not with a React component. Perfect for returning JSON, or in this case, an RSS feed.

To create a resource route, you just need to create a file in the app/routes directory. The file name will be the route, and the file contents will be the data that gets returned. I’ve created a file at app/routes/[rss.xml].tsx that will return a RSS feed of my posts.

The reason for the `[` and `]` around the file name is because remix treats a file name with a period `.` into slashes `/`. So if the filename were `rss.xml.tsx`, the route would be `/rss/xml` instead of the `/rss.xml` we're looking for.
// app/routes/[rss.xml].tsx
export type RssPost = {
  title: string;
  link: string;
  description: string;
  pubDate: string;
  guid?: string;
  author?: string;
};

/**
 * Generates an RSS feed from a list of posts.
 * @param title Title of the RSS feed
 * @param description A description of the RSS feed
 * @param link Link to the main page for the RSS feed
 * @param posts List of posts to include in the feed
 */
export function generateRss({
  description,
  posts,
  link,
  title
}: {
  title: string;
  description: string;
  link: string;
  posts: RssPost[];
}): string {
  const rssHeader = `<?xml version="1.0" encoding="UTF-8"?>
    <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
      <channel>
        <title>${title}</title>
        <description>${description}</description>
        <link>${link}</link>
        <language>en-us</language>
        <ttl>60</ttl>
        <atom:link href="https://keith.is/rss.xml" rel="self" type="application/rss+xml" />`;

  const rssBody = posts
    .map(
      post => `
          <item>
            <title><![CDATA[${post.title}]]></title>
            <description><![CDATA[${post.description}]]></description>
            <pubDate>${post.pubDate}</pubDate>
            <link>${post.link}</link>
            <guid isPermaLink="false">${post.link}</guid>
          </item>`
    )
    .join('');

  const rssFooter = `
      </channel>
    </rss>`;

  return rssHeader + rssBody + rssFooter;
}

The generateRss function takes a title, description, link, and list of posts and returns a string of the RSS feed. In the loader function, we’ll use this function to generate the RSS feed.

// app/routes/[rss.xml].tsx
export const loader: LoaderFunction = async () => {
  const posts = await getPosts();
  const feed = generateRss({
    title: "Keith's Blog",
    description: 'A blog about web development and other things.',
    link: 'https://keith.is',
    posts: posts.map(post => ({
      title: post.title,
      link: `https://keith.is/post/${post.slug}`,
      description: post.excerpt,
      pubDate: new Date(post.date).toUTCString()
    }))
  });

  return new Response(feed, {
    headers: {
      'Content-Type': 'application/xml',
      'Cache-Control': 'public, max-age=2419200'
    }
  });
};
  • getPosts() is my function for getting posts from markdown, but you can replace that with any method, like connecting to an API with fetch
  • The headers ensure that the page is XML, and that it’s cached for 4 weeks

If you browse to /rss.xml, you’ll see your brand new RSS feed ✨! You can find mine here. It ends up looking like:


<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Keith's Blog</title>
    <description>A blog about web development and other things.</description>
    <link>https://keith.is</link>
    <language>en-us</language>
    <ttl>60</ttl>
    <atom:link href="https://keith.is/rss.xml" rel="self" type="application/rss+xml" />
      <item>
        <title><![CDATA[Remix RSS feed]]></title>
        <description><![CDATA[Building this site, I had to add a RSS feed for my posts. Using remix resource routes, it's a snap.]]></description>
        <pubDate>Thu, 12 Jan 2023 00:00:00 GMT</pubDate>
        <link>https://keith.is/post/remix-rss</link>
        <guid isPermaLink="false">https://keith.is/post/remix-rss</guid>
      </item>
      <item>
        <title><![CDATA[New year, new site]]></title>
        <description><![CDATA[Creating a new site for 2023, focused on writing more and simplifying the process of getting content up. and bigger emojis.]]></description>
        <pubDate>Sun, 01 Jan 2023 00:00:00 GMT</pubDate>
        <link>https://keith.is/post/new-year-new-site-part-20</link>
        <guid isPermaLink="false">https://keith.is/post/new-year-new-site-part-20</guid>
      </item>
  </channel>
</rss>

Make sure to add the link to your RSS feed to your site. I added it to the footer of my site, and more importantly as a link tag.

// app/root.tsx
export const links: LinksFunction = () => {
  return [{rel: 'alternate', type: 'application/rss+xml', href: '/rss.xml'}];
};